From c1fe269121c7d7121f5b6df851da94cdd577b25f Mon Sep 17 00:00:00 2001 From: Quentin Date: Sat, 15 Apr 2023 20:20:22 +0200 Subject: [PATCH 001/143] deps: Replace minestom by paper --- build.gradle.kts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index e1fc7b48..5f540546 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,10 +11,11 @@ plugins { repositories { mavenCentral() maven("https://jitpack.io") + maven("https://repo.papermc.io/repository/maven-public/") } dependencies { - val minestomVersion = "aebf72de90" + val paperVersion = "1.19.4-R0.1-SNAPSHOT" val loggingVersion = "3.0.5" val mockkVersion = "1.13.4" val coroutinesVersion = "1.6.4" @@ -30,7 +31,7 @@ dependencies { api("org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlinSerializationVersion") api("org.jetbrains.kotlinx:kotlinx-serialization-hocon:$kotlinSerializationVersion") - api("com.github.Minestom.Minestom:Minestom:$minestomVersion") + api("io.papermc.paper:paper-api:$paperVersion") api("commons-net:commons-net:$commonsNetVersion") api("com.ibm.icu:icu4j:$icu4jVersion") api("net.kyori:adventure-text-minimessage:$minimessageVersion") @@ -42,7 +43,6 @@ dependencies { testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion") testImplementation("io.mockk:mockk:$mockkVersion") - testImplementation("com.github.Minestom.Minestom:testing:$minestomVersion") } kotlin { From 421fd79131a098b913093b2052a6e558e4ef5c00 Mon Sep 17 00:00:00 2001 From: Quentin Date: Sun, 16 Apr 2023 00:56:40 +0200 Subject: [PATCH 002/143] deps: Implements commandAPI + brigadier --- build.gradle.kts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 5f540546..d726d5b9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,10 +12,13 @@ repositories { mavenCentral() maven("https://jitpack.io") maven("https://repo.papermc.io/repository/maven-public/") + maven("https://repo.codemc.org/repository/maven-public/") + maven("https://libraries.minecraft.net") } dependencies { val paperVersion = "1.19.4-R0.1-SNAPSHOT" + val commandApiVersion = "8.8.0" val loggingVersion = "3.0.5" val mockkVersion = "1.13.4" val coroutinesVersion = "1.6.4" @@ -32,6 +35,10 @@ dependencies { api("org.jetbrains.kotlinx:kotlinx-serialization-hocon:$kotlinSerializationVersion") api("io.papermc.paper:paper-api:$paperVersion") + api("dev.jorel:commandapi-kotlin:$commandApiVersion") + api("dev.jorel:commandapi-shade:$commandApiVersion") + api("com.mojang:brigadier:1.0.18") + api("commons-net:commons-net:$commonsNetVersion") api("com.ibm.icu:icu4j:$icu4jVersion") api("net.kyori:adventure-text-minimessage:$minimessageVersion") From cb82aa2791181f19c8080165eefef6c926e633df Mon Sep 17 00:00:00 2001 From: Quentin Date: Sun, 16 Apr 2023 00:57:23 +0200 Subject: [PATCH 003/143] fix: Implements paper imports --- .../com/github/rushyverse/api/command/CommandMessages.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/command/CommandMessages.kt b/src/main/kotlin/com/github/rushyverse/api/command/CommandMessages.kt index 6e4cf307..aab67ad2 100644 --- a/src/main/kotlin/com/github/rushyverse/api/command/CommandMessages.kt +++ b/src/main/kotlin/com/github/rushyverse/api/command/CommandMessages.kt @@ -2,8 +2,8 @@ package com.github.rushyverse.api.command import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.NamedTextColor -import net.minestom.server.command.CommandSender -import net.minestom.server.entity.Player +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player /** * Utils class to send specific messages. From bde7575a9eeba60f3b249ff6d5a9b9d70657eef0 Mon Sep 17 00:00:00 2001 From: Quentin Date: Sun, 16 Apr 2023 01:00:06 +0200 Subject: [PATCH 004/143] chore: Delete commands that are already in paper --- .../com/github/rushyverse/api/RushyServer.kt | 8 +- .../rushyverse/api/command/GamemodeCommand.kt | 177 ------------------ .../rushyverse/api/command/GiveCommand.kt | 130 ------------- .../rushyverse/api/command/KickCommand.kt | 118 ------------ .../rushyverse/api/command/StopCommand.kt | 57 ------ 5 files changed, 2 insertions(+), 488 deletions(-) delete mode 100644 src/main/kotlin/com/github/rushyverse/api/command/GamemodeCommand.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/command/GiveCommand.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/command/KickCommand.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/command/StopCommand.kt diff --git a/src/main/kotlin/com/github/rushyverse/api/RushyServer.kt b/src/main/kotlin/com/github/rushyverse/api/RushyServer.kt index 2620611a..628725c9 100644 --- a/src/main/kotlin/com/github/rushyverse/api/RushyServer.kt +++ b/src/main/kotlin/com/github/rushyverse/api/RushyServer.kt @@ -38,13 +38,9 @@ public abstract class RushyServer { /** * Register the API commands in the [CommandManager]. - * @param manager CommandManager to register the commands in. */ - public fun registerCommands(manager: CommandManager = MinecraftServer.getCommandManager()) { - manager.register(StopCommand()) - manager.register(KickCommand()) - manager.register(GiveCommand()) - manager.register(GamemodeCommand()) + public fun registerCommands( ) { + // New commands here } } diff --git a/src/main/kotlin/com/github/rushyverse/api/command/GamemodeCommand.kt b/src/main/kotlin/com/github/rushyverse/api/command/GamemodeCommand.kt deleted file mode 100644 index bd3ea49b..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/command/GamemodeCommand.kt +++ /dev/null @@ -1,177 +0,0 @@ -package com.github.rushyverse.api.command - -import com.github.rushyverse.api.command.CommandMessages.sendMissingPermissionMessage -import com.github.rushyverse.api.command.CommandMessages.sendPlayerNotFoundMessage -import com.github.rushyverse.api.extension.sync -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.format.NamedTextColor -import net.minestom.server.command.CommandSender -import net.minestom.server.command.builder.Command -import net.minestom.server.command.builder.arguments.ArgumentEnum -import net.minestom.server.command.builder.arguments.ArgumentType -import net.minestom.server.command.builder.arguments.minecraft.ArgumentEntity -import net.minestom.server.entity.GameMode -import net.minestom.server.entity.Player -import net.minestom.server.permission.Permission - -/** - * Command to define the game mode of a player. - */ -public class GamemodeCommand : Command("gamemode") { - - /** - * Enum of permission to perform [command][GamemodeCommand]. - * @property permission Permission. - */ - public enum class Permissions(public val permission: Permission) { - /** - * Permission to change game mode of oneself. - */ - SELF(Permission("gamemode.self")), - - /** - * Permission to change game mode of another player. - */ - OTHER(Permission("gamemode.other")) - } - - init { - val gamemodeArg = createGamemodeArgument() - val playerArg = argumentPlayer() - - setCondition { sender, _ -> - sender !is Player || sender.hasPermission(Permissions.SELF.permission) || sender.hasPermission(Permissions.OTHER.permission) - } - - setDefaultExecutor { sender, context -> - val commandName = context.commandName - sendSyntaxMessage(sender, commandName) - } - - addSelfSyntax(gamemodeArg) - addOtherSyntax(playerArg, gamemodeArg) - } - - /** - * Send an error message to define the usage syntax of the command. - * @param sender Command's sender. - * @param commandName Name of the command used to execute it. - */ - private fun sendSyntaxMessage(sender: CommandSender, commandName: String) { - sender.sendMessage(Component.text("Usage: /$commandName [target]", NamedTextColor.RED)) - } - - /** - * Create a new argument targeting players name. - * @return New argument. - */ - private fun argumentPlayer(): ArgumentEntity = ArgumentType.Entity("target").onlyPlayers(true).singleEntity(true) - - /** - * Create a new argument targeting the gamemode choice. - * @return New argument. - */ - private fun createGamemodeArgument(): ArgumentEnum { - return ArgumentType.Enum("gamemode", GameMode::class.java).setFormat(ArgumentEnum.Format.LOWER_CASED) - .apply { - setCallback { sender, exception -> - sender.sendMessage( - Component.text("Invalid gamemode ", NamedTextColor.RED) - .append(Component.text(exception.input, NamedTextColor.WHITE)) - .append(Component.text("!")) - ) - } - } - } - - /** - * Define the syntax to process the command on another player. - * @param playerArg Argument to retrieve player(s) selected. - * @param gamemodeArg Argument to retrieve game mode selected. - */ - private fun addOtherSyntax(playerArg: ArgumentEntity, gamemodeArg: ArgumentEnum) { - addConditionalSyntax( - { sender, _ -> - sender !is Player || sender.hasPermission(Permissions.OTHER.permission) - }, - { sender, context -> - val finder = context.get(playerArg) - val player = finder.findFirstPlayer(sender) - if (player == null) { - sendPlayerNotFoundMessage(sender) - return@addConditionalSyntax - } - - if (player == sender) { - if (!sender.hasPermission(Permissions.SELF.permission)) { - sendMissingPermissionMessage(sender) - return@addConditionalSyntax - } - - processSelf(player, context.get(gamemodeArg)) - return@addConditionalSyntax - } - - processOther(sender, player, context.get(gamemodeArg)) - }, gamemodeArg, playerArg - ) - } - - /** - * Define the syntax to process the command on the sender. - * @param gamemodeArg Argument to retrieve game mode selected. - */ - private fun addSelfSyntax(gamemodeArg: ArgumentEnum) { - addSyntax({ sender, context -> - if (sender !is Player || !sender.hasPermission(Permissions.SELF.permission)) { - sendMissingPermissionMessage(sender) - return@addSyntax - } - - processSelf(sender, context.get(gamemodeArg)) - }, gamemodeArg) - } - - /** - * Change the game mode of the player and notify it. - * @param player Player who has his game mode modified. - * @param gamemode Game mode applied to the player. - */ - private fun processSelf(player: Player, gamemode: GameMode) { - val gamemodeComponent = createTranslatableGameModeComponent(gamemode) - changeGameModeSafe(player, gamemode) - player.sendMessage(Component.translatable("commands.gamemode.success.self", gamemodeComponent)) - } - - /** - * Change the game m ode of the targeted players and notify them. - * @param sender Command's sender. - * @param player List of entities targeted by the sender. - * @param gamemode Game mode applied to the players. - */ - private fun processOther(sender: CommandSender, player: Player, gamemode: GameMode) { - val gamemodeComponent = createTranslatableGameModeComponent(gamemode) - val playerName: Component = player.displayName ?: player.name - changeGameModeSafe(player, gamemode) - player.sendMessage(Component.translatable("gameMode.changed", gamemodeComponent)) - sender.sendMessage(Component.translatable("commands.gamemode.success.other", playerName, gamemodeComponent)) - } - - /** - * Get a safe instance of the player to assign the new game mode. - * @param player Player who has his game mode modified. - * @param gamemode GameMode applied to the player. - */ - private fun changeGameModeSafe(player: Player, gamemode: GameMode) { - player.sync { gameMode = gamemode } - } - - /** - * Create a component representing the game mode. - * @param gamemode Game mode. - * @return Component representing the game mode. - */ - private fun createTranslatableGameModeComponent(gamemode: GameMode): Component { - return Component.translatable("gameMode." + gamemode.name.lowercase()) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/command/GiveCommand.kt b/src/main/kotlin/com/github/rushyverse/api/command/GiveCommand.kt deleted file mode 100644 index 87b55fd3..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/command/GiveCommand.kt +++ /dev/null @@ -1,130 +0,0 @@ -package com.github.rushyverse.api.command - -import com.github.rushyverse.api.extension.sync -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.format.NamedTextColor -import net.minestom.server.command.CommandSender -import net.minestom.server.command.builder.Command -import net.minestom.server.command.builder.arguments.Argument -import net.minestom.server.command.builder.arguments.ArgumentType -import net.minestom.server.command.builder.arguments.minecraft.ArgumentEntity -import net.minestom.server.command.builder.arguments.minecraft.ArgumentItemStack -import net.minestom.server.entity.Player -import net.minestom.server.item.ItemStack -import net.minestom.server.permission.Permission - -/** - * Command to give item to a player. - */ -public class GiveCommand : Command("give") { - - /** - * Enum of permission to perform [command][GiveCommand]. - * @property permission Permission. - */ - public enum class Permissions(public val permission: Permission) { - /** - * Permission to give item to oneself. - */ - EXECUTE(Permission("give")), - } - - init { - setCondition { sender, _ -> - sender !is Player || sender.hasPermission(Permissions.EXECUTE.permission) - } - - setDefaultExecutor { sender, context -> - val commandName = context.commandName - sendSyntaxMessage(sender, commandName) - } - - val playersArg = argumentPlayers() - val itemArg = argumentItem() - val amountArg = argumentAmount() - - setSyntax(playersArg, itemArg, amountArg) - } - - /** - * Send an error message to define the usage syntax of the command. - * @param sender Command's sender. - * @param commandName Name of the command used to execute it. - */ - private fun sendSyntaxMessage(sender: CommandSender, commandName: String) { - sender.sendMessage(Component.text("Usage: /$commandName [amount]", NamedTextColor.RED)) - } - - /** - * Create a new argument targeting players name. - * @return New argument. - */ - private fun argumentPlayers(): ArgumentEntity = - ArgumentType.Entity("targets").singleEntity(false) - - /** - * Create a new argument targeting the amount of item. - * @return New argument. - */ - private fun argumentAmount() = ArgumentType.Integer("amount").setDefaultValue(1) - - /** - * Create a new argument targeting item to give. - * @return New argument. - */ - private fun argumentItem(): ArgumentItemStack = ArgumentType.ItemStack("item") - - /** - * Define the syntax to process the command. - */ - private fun setSyntax(playersArg: ArgumentEntity, itemArg: ArgumentItemStack, amountArg: Argument) { - addSyntax({ sender, context -> - val targets = context.get(playersArg).find(sender).asSequence().filterIsInstance() - - val amount = context.get(amountArg) - val item = context.get(itemArg).withAmount(amount) - - process(sender, targets, item) - }, playersArg, itemArg, amountArg) - } - - /** - * Give the item and notify sender. - * @param sender Command's sender. - * @param targets Player who will receive the item. - * @param item Item given. - */ - private fun process( - sender: CommandSender, - targets: Sequence, - item: ItemStack - ) { - val receivers = targets.map { - it.sync { - inventory.addItemStack(item) - name - } - }.toList() - - if (receivers.size == 1) { - sender.sendMessage( - Component.translatable( - "commands.give.success.single", - Component.text(item.amount()), - Component.text(item.material().name()), - receivers.first() - ) - ) - } else { - sender.sendMessage( - Component.translatable( - "commands.give.success.multiple", - Component.text(item.amount()), - Component.text(item.material().name()), - Component.text(receivers.size) - ) - ) - } - - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/command/KickCommand.kt b/src/main/kotlin/com/github/rushyverse/api/command/KickCommand.kt deleted file mode 100644 index 974f745e..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/command/KickCommand.kt +++ /dev/null @@ -1,118 +0,0 @@ -package com.github.rushyverse.api.command - -import com.github.rushyverse.api.command.CommandMessages.sendPlayerNotFoundMessage -import com.github.rushyverse.api.extension.sync -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.format.NamedTextColor -import net.minestom.server.command.CommandSender -import net.minestom.server.command.builder.Command -import net.minestom.server.command.builder.arguments.Argument -import net.minestom.server.command.builder.arguments.ArgumentType -import net.minestom.server.command.builder.arguments.minecraft.ArgumentEntity -import net.minestom.server.entity.Player -import net.minestom.server.permission.Permission - -/** - * Command to kick a player. - * @param isProtected Lambda to check if a player is protected from being kicked. - */ -public class KickCommand( - private val isProtected: (CommandSender, Player) -> Boolean = { _, target -> - target.sync { hasPermission(Permissions.EXECUTE.permission) } - } -) : Command("kick") { - - /** - * Enum of permission to perform [command][KickCommand]. - * @property permission Permission. - */ - public enum class Permissions(public val permission: Permission) { - /** - * Permission to kick another player. - */ - EXECUTE(Permission("kick")) - } - - init { - setDefaultExecutor { sender, context -> - val commandName = context.commandName - sendSyntaxMessage(sender, commandName) - } - - setCondition { sender, _ -> - sender !is Player || sender.hasPermission(Permissions.EXECUTE.permission) - } - - val playerArg = argumentPlayer() - val reasonArg = argumentReason() - addSyntaxPlayer(playerArg, reasonArg) - } - - /** - * Create a new argument to retrieve the reason of the kick. - * @return New string argument - */ - private fun argumentReason(): Argument = - ArgumentType.String("reason").setDefaultValue("No reason specified") - - /** - * Create a new argument targeting players name. - * @return New argument. - */ - private fun argumentPlayer(): ArgumentEntity = - ArgumentType.Entity("target").onlyPlayers(true).singleEntity(true) - - /** - * Send an error message to define the usage syntax of the command. - * @param sender Command's sender. - * @param commandName Name of the command used to execute it. - */ - private fun sendSyntaxMessage(sender: CommandSender, commandName: String) { - sender.sendMessage(Component.text("Usage: /$commandName ", NamedTextColor.RED)) - } - - /** - * Define the syntax to process the command on a player. - * @param playerArg Argument to retrieve player selected. - */ - private fun addSyntaxPlayer(playerArg: ArgumentEntity, reasonArg: Argument) { - addSyntax({ sender, context -> - val player = context.get(playerArg).findFirstPlayer(sender) - if (player == null) { - sendPlayerNotFoundMessage(sender) - return@addSyntax - } - - if (!isProtected(sender, player)) { - sender.sendMessage(Component.text("You can't kick this player", NamedTextColor.RED)) - return@addSyntax - } - - val reasonComponent = Component.text(context.get(reasonArg)) - - kickPlayer(player, reasonComponent) - - sender.sendMessage( - Component.translatable("commands.kick.success", player.name, reasonComponent) - .color(NamedTextColor.YELLOW) - ) - }, playerArg, reasonArg) - } - - /** - * Kick the player from the server.ad - * - * @param player Player to kick. - * @param reason Component to display as the reason of the kick. - */ - private fun kickPlayer( - player: Player, - reason: Component - ) { - val kickComponent = Component.translatable("multiplayer.disconnect.kicked") - .appendNewline() - .append(reason) - - player.sync { kick(kickComponent) } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/command/StopCommand.kt b/src/main/kotlin/com/github/rushyverse/api/command/StopCommand.kt deleted file mode 100644 index 0c6d3313..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/command/StopCommand.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.github.rushyverse.api.command - -import com.github.rushyverse.api.extension.async -import com.github.rushyverse.api.extension.setDefaultExecutorSuspend -import kotlinx.coroutines.awaitAll -import net.kyori.adventure.text.Component -import net.minestom.server.MinecraftServer -import net.minestom.server.ServerProcess -import net.minestom.server.command.builder.Command -import net.minestom.server.entity.Player -import net.minestom.server.instance.InstanceManager -import net.minestom.server.permission.Permission - -/** - * Command to stop the server. - */ -public class StopCommand( - private val serverProcess: ServerProcess = MinecraftServer.process(), - private val instanceManager: InstanceManager? = MinecraftServer.getInstanceManager() -) : Command("stop") { - - /** - * Enum of permission to perform [command][StopCommand]. - * @property permission Permission. - */ - public enum class Permissions(public val permission: Permission) { - /** - * Permission to give item to another player. - */ - EXECUTE(Permission("stop")) - } - - init { - setCondition { sender, _ -> - sender !is Player || sender.hasPermission(Permissions.EXECUTE.permission) - } - - setDefaultExecutorSuspend { _, _ -> - kickPlayers() - serverProcess.stop() - } - } - - /** - * Kick all players from the server. - */ - private suspend fun kickPlayers() { - val instanceManager = instanceManager ?: return - val stopComponent = Component.translatable("commands.stop.stopping") - instanceManager.instances - .asSequence() - .flatMap { it.players } - .map { - it.async { kick(stopComponent) } - }.toList().awaitAll() - } -} \ No newline at end of file From 9fa0f82c6b2ce0f4614da58d268ad89c9f8dc31f Mon Sep 17 00:00:00 2001 From: Quentin Date: Thu, 27 Apr 2023 21:57:53 +0200 Subject: [PATCH 005/143] chore: Location migration --- .../rushyverse/api/extension/LocationExt.kt | 38 +++++++++++++++++++ .../github/rushyverse/api/extension/PosExt.kt | 37 ------------------ 2 files changed, 38 insertions(+), 37 deletions(-) create mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/LocationExt.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/PosExt.kt diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/LocationExt.kt b/src/main/kotlin/com/github/rushyverse/api/extension/LocationExt.kt new file mode 100644 index 00000000..ac226d96 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/extension/LocationExt.kt @@ -0,0 +1,38 @@ +package com.github.rushyverse.api.extension + +import org.bukkit.Location +import kotlin.math.pow +import kotlin.math.sqrt + +/** + * Returns the center location between two points. + * @receiver The first location. + * @param other The second location. + * @return The center location between the two points. + */ +public fun Location.centerRelative(other: Location): Location = + add(other).multiply(0.5) + +/** + * Returns whether the given [Location] is in the cube defined by the two given [Location]s. + * @receiver The location to check. + * @param min The minimum location of the cube. + * @param max The maximum location of the cube. + * @return `true` if the location is in the cube, `false` otherwise. + */ +public fun Location.isInCube(min: Location, max: Location): Boolean { + return x in min.x..max.x && y in min.y..max.y && z in min.z..max.z +} + +/** + * Returns whether the given [Location] is in the cylinder defined by the given [locationCylinder], [radius] and height defined by [limitY]. + * @receiver The location to check. + * @param locationCylinder The location of the cylinder. + * @param radius The radius of the cylinder. + * @param limitY The height of the cylinder. + * @return `true` if the location is in the cylinder, `false` otherwise. + */ +public fun Location.isInCylinder(locationCylinder: Location, radius: Double, limitY: ClosedRange): Boolean { + val distance = sqrt((x - locationCylinder.x).pow(2.0) + (z - locationCylinder.z).pow(2.0)) + return distance <= radius && y in limitY +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/PosExt.kt b/src/main/kotlin/com/github/rushyverse/api/extension/PosExt.kt deleted file mode 100644 index a3282614..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/extension/PosExt.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.github.rushyverse.api.extension - -import net.minestom.server.coordinate.Pos -import kotlin.math.pow -import kotlin.math.sqrt - -/** - * Returns the center position between two points. - * @receiver The first position. - * @param other The second position. - * @return The center position between the two points. - */ -public fun Pos.centerRelative(other: Pos): Pos = add(other).div(2.0) - -/** - * Returns whether the given [Pos] is in the cube defined by the two given [Pos]s. - * @receiver The position to check. - * @param min The minimum position of the cube. - * @param max The maximum position of the cube. - * @return `true` if the position is in the cube, `false` otherwise. - */ -public fun Pos.isInCube(min: Pos, max: Pos): Boolean { - return x in min.x..max.x && y in min.y..max.y && z in min.z..max.z -} - -/** - * Returns whether the given [Pos] is in the cylinder defined by the given [positionCylinder], [radius] and height defined by [limitY]. - * @receiver The position to check. - * @param positionCylinder The position of the cylinder. - * @param radius The radius of the cylinder. - * @param limitY The height of the cylinder. - * @return `true` if the position is in the cylinder, `false` otherwise. - */ -public fun Pos.isInCylinder(positionCylinder: Pos, radius: Double, limitY: ClosedRange): Boolean { - val distance = sqrt((x - positionCylinder.x).pow(2.0) + (z - positionCylinder.z).pow(2.0)) - return distance <= radius && y in limitY -} \ No newline at end of file From 3cffbab59e09dfe480719bda46491af8a58b28fa Mon Sep 17 00:00:00 2001 From: Quentin Date: Thu, 27 Apr 2023 21:58:47 +0200 Subject: [PATCH 006/143] chore: Area migration --- .../rushyverse/api/position/AbstractArea.kt | 2 +- .../rushyverse/api/position/CubeArea.kt | 46 +++++++++---------- .../rushyverse/api/position/CylinderArea.kt | 22 ++++----- .../github/rushyverse/api/position/IArea.kt | 2 +- .../rushyverse/api/position/IAreaLocatable.kt | 16 +++---- .../rushyverse/api/position/MultiArea.kt | 2 +- .../rushyverse/api/position/SphereArea.kt | 18 ++++---- 7 files changed, 54 insertions(+), 54 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/position/AbstractArea.kt b/src/main/kotlin/com/github/rushyverse/api/position/AbstractArea.kt index 92d5b38b..73fad1c4 100644 --- a/src/main/kotlin/com/github/rushyverse/api/position/AbstractArea.kt +++ b/src/main/kotlin/com/github/rushyverse/api/position/AbstractArea.kt @@ -1,6 +1,6 @@ package com.github.rushyverse.api.position -import net.minestom.server.entity.Entity +import org.bukkit.entity.Entity import java.util.* /** diff --git a/src/main/kotlin/com/github/rushyverse/api/position/CubeArea.kt b/src/main/kotlin/com/github/rushyverse/api/position/CubeArea.kt index 631c56a9..f3384ef7 100644 --- a/src/main/kotlin/com/github/rushyverse/api/position/CubeArea.kt +++ b/src/main/kotlin/com/github/rushyverse/api/position/CubeArea.kt @@ -3,9 +3,9 @@ package com.github.rushyverse.api.position import com.github.rushyverse.api.extension.centerRelative import com.github.rushyverse.api.extension.isInCube import com.github.rushyverse.api.extension.minMaxOf -import net.minestom.server.coordinate.Pos -import net.minestom.server.entity.Entity -import net.minestom.server.instance.Instance +import org.bukkit.Location +import org.bukkit.World +import org.bukkit.entity.Entity /** * A cuboid area defined by two positions. @@ -17,47 +17,47 @@ import net.minestom.server.instance.Instance */ public class CubeArea( public val entityClass: Class, - public override var instance: Instance, - position1: Pos, - position2: Pos + public override var world: World, + location1: Location, + location2: Location ) : AbstractArea(), IAreaLocatable { public companion object { public inline operator fun invoke( - instance: Instance, - position1: Pos, - position2: Pos - ): CubeArea = CubeArea(E::class.java, instance, position1, position2) + world: World, + location1: Location, + location2: Location + ): CubeArea = CubeArea(E::class.java, world, location1, location2) } - override var position: Pos + override var location: Location get() = max.centerRelative(min) set(value) { // The new position becomes the center of the cube. - val halfSize = max.centerRelative(min) - min = value.sub(halfSize) - max = value.add(halfSize) + val halfSize = max.toVector().subtract(min.toVector()).multiply(0.5) + min = value.toVector().subtract(halfSize).toLocation(value.world) + max = value.toVector().add(halfSize).toLocation(value.world) } - public var min: Pos + public var min: Location private set - public var max: Pos + public var max: Location private set init { - val (x1, x2) = minMaxOf(position1.x(), position2.x()) - val (y1, y2) = minMaxOf(position1.y(), position2.y()) - val (z1, z2) = minMaxOf(position1.z(), position2.z()) - this.min = Pos(x1, y1, z1) - this.max = Pos(x2, y2, z2) + val (x1, x2) = minMaxOf(location1.x(), location2.x()) + val (y1, y2) = minMaxOf(location1.y(), location2.y()) + val (z1, z2) = minMaxOf(location1.z(), location2.z()) + this.min = Location(world, x1, y1, z1) + this.max = Location(world, x2, y2, z2) } override fun updateEntitiesInArea(): Pair, Collection> { - return update(instance.entities + return update(world.entities .asSequence() .filterIsInstance(entityClass) - .filter { it.position.isInCube(min, max) } + .filter { it.location.isInCube(min, max) } .toSet() ) } diff --git a/src/main/kotlin/com/github/rushyverse/api/position/CylinderArea.kt b/src/main/kotlin/com/github/rushyverse/api/position/CylinderArea.kt index e36b6bf2..ea52db6f 100644 --- a/src/main/kotlin/com/github/rushyverse/api/position/CylinderArea.kt +++ b/src/main/kotlin/com/github/rushyverse/api/position/CylinderArea.kt @@ -1,9 +1,9 @@ package com.github.rushyverse.api.position import com.github.rushyverse.api.extension.isInCylinder -import net.minestom.server.coordinate.Pos -import net.minestom.server.entity.Entity -import net.minestom.server.instance.Instance +import org.bukkit.Location +import org.bukkit.World +import org.bukkit.entity.Entity /** * An area defined by a cylinder shape. @@ -14,19 +14,19 @@ import net.minestom.server.instance.Instance */ public class CylinderArea( public val entityClass: Class, - public override var instance: Instance, - public override var position: Pos, + public override var world: World, + public override var location: Location, radius: Double, public var limitY: ClosedRange, ) : AbstractArea(), IAreaLocatable { public companion object { public inline operator fun invoke( - instance: Instance, - position: Pos, + world: World, + location: Location, radius: Double, limitY: ClosedRange - ): CylinderArea = CylinderArea(E::class.java, instance, position, radius, limitY) + ): CylinderArea = CylinderArea(E::class.java, world, location, radius, limitY) } public var radius: Double = radius @@ -48,12 +48,12 @@ public class CylinderArea( } override fun updateEntitiesInArea(): Pair, Collection> { - val cylinderPosition = position + val cylinderPosition = location return update( - instance.entities + world.entities .asSequence() .filterIsInstance(entityClass) - .filter { it.position.isInCylinder(cylinderPosition, radius, limitY) } + .filter { it.location.isInCylinder(cylinderPosition, radius, limitY) } .toSet() ) } diff --git a/src/main/kotlin/com/github/rushyverse/api/position/IArea.kt b/src/main/kotlin/com/github/rushyverse/api/position/IArea.kt index 8da856a2..51317718 100644 --- a/src/main/kotlin/com/github/rushyverse/api/position/IArea.kt +++ b/src/main/kotlin/com/github/rushyverse/api/position/IArea.kt @@ -1,6 +1,6 @@ package com.github.rushyverse.api.position -import net.minestom.server.entity.Entity +import org.bukkit.entity.Entity /** * An area that contains entities. diff --git a/src/main/kotlin/com/github/rushyverse/api/position/IAreaLocatable.kt b/src/main/kotlin/com/github/rushyverse/api/position/IAreaLocatable.kt index 19e7b34a..138655b8 100644 --- a/src/main/kotlin/com/github/rushyverse/api/position/IAreaLocatable.kt +++ b/src/main/kotlin/com/github/rushyverse/api/position/IAreaLocatable.kt @@ -1,18 +1,18 @@ package com.github.rushyverse.api.position -import net.minestom.server.coordinate.Pos -import net.minestom.server.entity.Entity -import net.minestom.server.instance.Instance +import org.bukkit.Location +import org.bukkit.World +import org.bukkit.entity.Entity /** - * An area with a position into an instance. + * An area with a location into an instance. * @param E Type of entity. - * @property instance Instance where is located the area. - * @property position Position of the area. + * @property world World where is located the area. + * @property location Location of the area. */ public interface IAreaLocatable : IArea { - public var instance: Instance + public var world: World - public var position: Pos + public var location: Location } \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/position/MultiArea.kt b/src/main/kotlin/com/github/rushyverse/api/position/MultiArea.kt index 2688a8f3..9fe95548 100644 --- a/src/main/kotlin/com/github/rushyverse/api/position/MultiArea.kt +++ b/src/main/kotlin/com/github/rushyverse/api/position/MultiArea.kt @@ -1,6 +1,6 @@ package com.github.rushyverse.api.position -import net.minestom.server.entity.Entity +import org.bukkit.entity.Entity /** * An area corresponding to multiple areas. diff --git a/src/main/kotlin/com/github/rushyverse/api/position/SphereArea.kt b/src/main/kotlin/com/github/rushyverse/api/position/SphereArea.kt index 4f0fd4c0..dfc9777d 100644 --- a/src/main/kotlin/com/github/rushyverse/api/position/SphereArea.kt +++ b/src/main/kotlin/com/github/rushyverse/api/position/SphereArea.kt @@ -1,8 +1,8 @@ package com.github.rushyverse.api.position -import net.minestom.server.coordinate.Pos -import net.minestom.server.entity.Entity -import net.minestom.server.instance.Instance +import org.bukkit.Location +import org.bukkit.World +import org.bukkit.entity.Entity /** * An area defined by a sphere shape. @@ -12,17 +12,17 @@ import net.minestom.server.instance.Instance */ public class SphereArea( public val entityClass: Class, - public override var instance: Instance, - public override var position: Pos, + public override var world: World, + public override var location: Location, radius: Double ) : AbstractArea(), IAreaLocatable { public companion object { public inline operator fun invoke( - instance: Instance, - position: Pos, + world: World, + location: Location, radius: Double - ): SphereArea = SphereArea(E::class.java, instance, position, radius) + ): SphereArea = SphereArea(E::class.java, world, location, radius) } public var radius: Double = radius @@ -44,6 +44,6 @@ public class SphereArea( } override fun updateEntitiesInArea(): Pair, Collection> { - return update(instance.getNearbyEntities(position, radius).asSequence().filterIsInstance(entityClass).toSet()) + return update(world.getNearbyEntities(location, radius, radius, radius).asSequence().filterIsInstance(entityClass).toSet()) } } \ No newline at end of file From 12f25106e41822af6e65dd98351009b5ec3dbadd Mon Sep 17 00:00:00 2001 From: Quentin Date: Thu, 27 Apr 2023 22:18:46 +0200 Subject: [PATCH 007/143] chore: Added ext function toPos() --- .../github/rushyverse/api/extension/LocationExt.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/LocationExt.kt b/src/main/kotlin/com/github/rushyverse/api/extension/LocationExt.kt index ac226d96..750d9022 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/LocationExt.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/LocationExt.kt @@ -1,5 +1,6 @@ package com.github.rushyverse.api.extension +import com.github.rushyverse.api.serializer.Pos import org.bukkit.Location import kotlin.math.pow import kotlin.math.sqrt @@ -35,4 +36,13 @@ public fun Location.isInCube(min: Location, max: Location): Boolean { public fun Location.isInCylinder(locationCylinder: Location, radius: Double, limitY: ClosedRange): Boolean { val distance = sqrt((x - locationCylinder.x).pow(2.0) + (z - locationCylinder.z).pow(2.0)) return distance <= radius && y in limitY -} \ No newline at end of file +} + +/** + * Converts this [Location] object to a [Pos] object. + * This function allows serializing coordinates without taking the world into account. + * + * @receiver The [Location] to convert. + * @return A new [Pos] object with the same position and rotation as this [Location] object. + */ +public fun Location.toPos(): Pos = Pos(x, y, z, yaw, pitch) From dffb2b3a0129d7f549b0add04c8d86e9e6e3d3e3 Mon Sep 17 00:00:00 2001 From: Quentin Date: Thu, 27 Apr 2023 22:19:38 +0200 Subject: [PATCH 008/143] fix: Reuse the Minestom Pos concept --- .../api/serializer/PosSerializer.kt | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/PosSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/PosSerializer.kt index 077d5684..a91ffb21 100644 --- a/src/main/kotlin/com/github/rushyverse/api/serializer/PosSerializer.kt +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/PosSerializer.kt @@ -7,7 +7,8 @@ import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.encoding.* -import net.minestom.server.coordinate.Pos +import org.bukkit.Location +import org.bukkit.World /** * Serializer for [Pos]. @@ -88,4 +89,24 @@ public object PosSerializer : KSerializer { ) } } -} \ No newline at end of file +} + +/** + * Represents a position in a 3D space with additional yaw and pitch rotations. + * Can be converted to a Bukkit [Location] with the provided [toLocation] method. + * + * @property x The x-coordinate of the position. + * @property y The y-coordinate of the position. + * @property z The z-coordinate of the position. + * @property yaw The yaw rotation (horizontal rotation) in degrees. + * @property pitch The pitch rotation (vertical rotation) in degrees. + */ +public data class Pos(val x: Double, val y: Double, val z: Double, val yaw: Float, val pitch: Float) { + /** + * Converts this [Pos] object to a Bukkit [Location] object. + * + * @param world The [World] in which the position is located. + * @return A new [Location] object representing the same position and rotation as this [Pos] object. + */ + public fun toLocation(world: World): Location = Location(world, x, y, z, yaw, pitch) +} From 47d0ff88ddf6c47d51844eea6c331ba3c18662a2 Mon Sep 17 00:00:00 2001 From: Quentin Date: Wed, 3 May 2023 12:53:44 +0200 Subject: [PATCH 009/143] fix: Use the bukkit ItemStack instead of Minestom --- .../kotlin/com/github/rushyverse/api/item/ItemComparator.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/item/ItemComparator.kt b/src/main/kotlin/com/github/rushyverse/api/item/ItemComparator.kt index 18fa8a4b..ef53863f 100644 --- a/src/main/kotlin/com/github/rushyverse/api/item/ItemComparator.kt +++ b/src/main/kotlin/com/github/rushyverse/api/item/ItemComparator.kt @@ -1,6 +1,6 @@ package com.github.rushyverse.api.item -import net.minestom.server.item.ItemStack +import org.bukkit.inventory.ItemStack /** * Allows to identify if an item is equivalent to another. From 55327031ef1ea44bcc7092ce40de7437dafa3976 Mon Sep 17 00:00:00 2001 From: Quentin Date: Wed, 3 May 2023 13:21:13 +0200 Subject: [PATCH 010/143] fix: Use the bukkit BlockFace calculate orientation --- .../github/rushyverse/api/image/MapImageMath.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/image/MapImageMath.kt b/src/main/kotlin/com/github/rushyverse/api/image/MapImageMath.kt index c18cded3..e8516203 100644 --- a/src/main/kotlin/com/github/rushyverse/api/image/MapImageMath.kt +++ b/src/main/kotlin/com/github/rushyverse/api/image/MapImageMath.kt @@ -1,6 +1,6 @@ package com.github.rushyverse.api.image -import net.minestom.server.entity.metadata.other.ItemFrameMeta +import org.bukkit.block.BlockFace /** * This class is used to calculate the position of the map image. @@ -21,12 +21,12 @@ public sealed interface MapImageMath { * Link the item frame orientation to the [MapImageMath] instance. */ private val orientations = mapOf( - ItemFrameMeta.Orientation.DOWN to Down, - ItemFrameMeta.Orientation.UP to Up, - ItemFrameMeta.Orientation.NORTH to North, - ItemFrameMeta.Orientation.SOUTH to South, - ItemFrameMeta.Orientation.WEST to West, - ItemFrameMeta.Orientation.EAST to East + BlockFace.DOWN to Down, + BlockFace.UP to Up, + BlockFace.NORTH to North, + BlockFace.SOUTH to South, + BlockFace.WEST to West, + BlockFace.EAST to East ) /** @@ -34,7 +34,7 @@ public sealed interface MapImageMath { * @param orientation The orientation of the item frame. * @return The [MapImageMath] for the orientation. */ - public fun getFromOrientation(orientation: ItemFrameMeta.Orientation): MapImageMath { + public fun getFromOrientation(orientation: BlockFace): MapImageMath { return orientations[orientation] ?: throw IllegalArgumentException("Unsupported orientation: $orientation") } } From 964972c11e5e8bf3cc50d6d3c7db6d4f6d0ebdf9 Mon Sep 17 00:00:00 2001 From: Quentin Date: Wed, 3 May 2023 13:42:42 +0200 Subject: [PATCH 011/143] fix: Adapt ItemStack extension --- .../rushyverse/api/extension/ItemStackExt.kt | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/ItemStackExt.kt b/src/main/kotlin/com/github/rushyverse/api/extension/ItemStackExt.kt index 45a7f96d..a9ec3241 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/ItemStackExt.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/ItemStackExt.kt @@ -3,7 +3,7 @@ package com.github.rushyverse.api.extension import net.kyori.adventure.text.Component import net.kyori.adventure.text.TextComponent import net.kyori.adventure.text.format.NamedTextColor -import net.minestom.server.item.ItemStack +import org.bukkit.inventory.ItemStack /** * Format the string [loreString] using [toFormattedLoreSequence] and transform it into a [TextComponent] using [toLore]. @@ -13,13 +13,18 @@ import net.minestom.server.item.ItemStack * @param transform A function that will be applied to each component. * @return The same builder with the lore modified. */ -public inline fun ItemStack.Builder.formattedLore( +public inline fun ItemStack.formattedLore( loreString: String, lineLength: Int = DEFAULT_LORE_LINE_LENGTH, crossinline transform: TextComponent.Builder.() -> Unit = { color(NamedTextColor.GRAY) } -): ItemStack.Builder = lore(loreString.toFormattedLoreSequence(lineLength).toLore(transform)) +): ItemStack { + val meta = this.itemMeta + meta.lore(loreString.toFormattedLoreSequence(lineLength).toLore(transform)) + this.itemMeta = meta + return this +} /** * Format the string [loreString] using [toFormattedLoreSequence] and transform it into a [TextComponent] using [toLore]. @@ -35,7 +40,12 @@ public inline fun ItemStack.withFormattedLore( crossinline transform: TextComponent.Builder.() -> Unit = { color(NamedTextColor.GRAY) } -): ItemStack = withLore(loreString.toFormattedLoreSequence(lineLength).toLore(transform)) +): ItemStack { + val meta = this.itemMeta + meta.lore(loreString.toFormattedLoreSequence(lineLength).toLore(transform)) + this.itemMeta = meta + return this +} /** * Define an unique component as lore. @@ -43,4 +53,6 @@ public inline fun ItemStack.withFormattedLore( * @param lore Lore to set. * @return The same item. */ -public fun ItemStack.withLore(lore: Component): ItemStack = withLore(listOf(lore)) \ No newline at end of file +public fun ItemStack.withLore(lore: Component): ItemStack = apply { + itemMeta = itemMeta.apply { lore(listOf(lore)) } +} From ca059fc46bc3254126e1b92633de6a4dcf01c18b Mon Sep 17 00:00:00 2001 From: Quentin Date: Thu, 4 May 2023 10:06:58 +0200 Subject: [PATCH 012/143] fix: Use Pos concept for configuration reader --- .../rushyverse/api/configuration/HoconConfigurationReader.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReader.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReader.kt index e725478a..2ce7d742 100644 --- a/src/main/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReader.kt +++ b/src/main/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReader.kt @@ -1,12 +1,12 @@ package com.github.rushyverse.api.configuration +import com.github.rushyverse.api.serializer.Pos import com.github.rushyverse.api.serializer.PosSerializer import com.typesafe.config.ConfigFactory import kotlinx.serialization.KSerializer import kotlinx.serialization.hocon.Hocon import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.serializer -import net.minestom.server.coordinate.Pos import java.io.File import kotlin.reflect.KClass import kotlin.reflect.full.createType From 1029b70ebdce3cfb658e01935ee62a3f3aa9be15 Mon Sep 17 00:00:00 2001 From: Quentin Date: Thu, 4 May 2023 10:07:38 +0200 Subject: [PATCH 013/143] fix: Use BlockFace for ItemFrame orientation serializer --- .../serializer/ItemFrameMetaOrientationSerializer.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/ItemFrameMetaOrientationSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/ItemFrameMetaOrientationSerializer.kt index c967f8a3..d1d6659d 100644 --- a/src/main/kotlin/com/github/rushyverse/api/serializer/ItemFrameMetaOrientationSerializer.kt +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/ItemFrameMetaOrientationSerializer.kt @@ -7,23 +7,23 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -import net.minestom.server.entity.metadata.other.ItemFrameMeta +import org.bukkit.block.BlockFace /** * Serializer for [ItemFrameMeta.Orientation]. * To deserialize the orientation, it will be case-insensitive. */ -public object ItemFrameMetaOrientationSerializer : KSerializer { +public object ItemFrameMetaOrientationSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("orientation", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: ItemFrameMeta.Orientation) { + override fun serialize(encoder: Encoder, value: BlockFace) { encoder.encodeString(value.name) } - override fun deserialize(decoder: Decoder): ItemFrameMeta.Orientation { + override fun deserialize(decoder: Decoder): BlockFace { val decodeString = decoder.decodeString() - return ItemFrameMeta.Orientation.values() + return BlockFace.values() .find { it.name.equals(decodeString, true) } ?: throw SerializationException("Invalid orientation") } From 8053c439e7f5d5bbfd402ff446151d14df238ad7 Mon Sep 17 00:00:00 2001 From: Quentin Date: Thu, 4 May 2023 10:12:01 +0200 Subject: [PATCH 014/143] fix: doc --- .../api/serializer/ItemFrameMetaOrientationSerializer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/ItemFrameMetaOrientationSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/ItemFrameMetaOrientationSerializer.kt index d1d6659d..07aaf5af 100644 --- a/src/main/kotlin/com/github/rushyverse/api/serializer/ItemFrameMetaOrientationSerializer.kt +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/ItemFrameMetaOrientationSerializer.kt @@ -10,7 +10,7 @@ import kotlinx.serialization.encoding.Encoder import org.bukkit.block.BlockFace /** - * Serializer for [ItemFrameMeta.Orientation]. + * Serializer for ItemFrame [BlockFace] orientation. * To deserialize the orientation, it will be case-insensitive. */ public object ItemFrameMetaOrientationSerializer : KSerializer { From eca8a292e2fbe5bfa60b7fb100c564f5402cd620 Mon Sep 17 00:00:00 2001 From: Quentin Date: Thu, 4 May 2023 10:27:19 +0200 Subject: [PATCH 015/143] fix: Adapt the class for a JavaPlugin --- .../com/github/rushyverse/api/RushyServer.kt | 66 +++---------------- 1 file changed, 8 insertions(+), 58 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/RushyServer.kt b/src/main/kotlin/com/github/rushyverse/api/RushyServer.kt index 628725c9..e2a7390c 100644 --- a/src/main/kotlin/com/github/rushyverse/api/RushyServer.kt +++ b/src/main/kotlin/com/github/rushyverse/api/RushyServer.kt @@ -1,25 +1,13 @@ package com.github.rushyverse.api -import com.github.rushyverse.api.command.GamemodeCommand -import com.github.rushyverse.api.command.GiveCommand -import com.github.rushyverse.api.command.KickCommand -import com.github.rushyverse.api.command.StopCommand import com.github.rushyverse.api.configuration.* import com.github.rushyverse.api.translation.ResourceBundleTranslationsProvider import com.github.rushyverse.api.translation.SupportedLanguage import com.github.rushyverse.api.translation.TranslationsProvider import com.github.rushyverse.api.translation.registerResourceBundleForSupportedLocales -import com.github.rushyverse.api.utils.workingDirectory import mu.KLogger import mu.KotlinLogging -import net.minestom.server.MinecraftServer -import net.minestom.server.command.CommandManager -import net.minestom.server.extras.MojangAuth -import net.minestom.server.extras.bungee.BungeeCordProxy -import net.minestom.server.extras.velocity.VelocityProxy -import net.minestom.server.instance.AnvilLoader -import net.minestom.server.instance.InstanceContainer -import java.io.File +import org.bukkit.plugin.java.JavaPlugin import java.util.* import kotlin.reflect.KClass @@ -28,7 +16,7 @@ public val logger: KLogger = KotlinLogging.logger { } /** * Abstract implementation of Minecraft server. */ -public abstract class RushyServer { +public abstract class RushyServer : JavaPlugin() { public companion object API { /** @@ -37,9 +25,9 @@ public abstract class RushyServer { public const val BUNDLE_API: String = "api" /** - * Register the API commands in the [CommandManager]. + * Register the API commands. */ - public fun registerCommands( ) { + public fun registerCommands() { // New commands here } } @@ -56,38 +44,18 @@ public abstract class RushyServer { */ protected suspend inline fun start( configurationPath: String? = null, - init: T.(InstanceContainer) -> Unit + init: (T) -> Unit ) { logger.info { "Loading configuration from $configurationPath" } val config = loadConfiguration(configurationPath) logger.info { "Configuration loaded" } - val minecraftServer = MinecraftServer.init() - val instanceManager = MinecraftServer.getInstanceManager() - val instanceContainer = instanceManager.createInstanceContainer() val serverConfig = config.server - loadWorld(serverConfig.world, instanceContainer) applyVelocityConfiguration(serverConfig.velocity) applyBungeeCordConfiguration(serverConfig.bungeeCord) - applyOnlineMode(serverConfig.onlineMode) - - init(config, instanceContainer) - - minecraftServer.start("0.0.0.0", serverConfig.port) - } - - /** - * Enable the online mode if [enabled] is true. - * @param enabled If the online mode should be enabled. - */ - protected open suspend fun applyOnlineMode(enabled: Boolean) { - if (enabled) { - logger.info { "Enabling Online mode" } - MojangAuth.init() - logger.info { "Online mode enabled" } - } + init(config) } /** @@ -97,7 +65,7 @@ public abstract class RushyServer { protected open suspend fun applyVelocityConfiguration(velocity: IVelocityConfiguration) { if (velocity.enabled) { logger.info { "Enabling Velocity support" } - VelocityProxy.enable(velocity.secret) + // TODO: Velocity configuration here logger.info { "Velocity support enabled" } } } @@ -109,29 +77,11 @@ public abstract class RushyServer { protected open suspend fun applyBungeeCordConfiguration(bungeeCord: IBungeeCordConfiguration) { if (bungeeCord.enabled) { logger.info { "Enabling BungeeCord support" } - BungeeCordProxy.enable() - BungeeCordProxy.setBungeeGuardTokens(bungeeCord.secrets) + // TODO: BungeeCord configuration here logger.info { "BungeeCord support enabled" } } } - /** - * With the [worldFolder], retrieve the file of the world and load it in the [instanceContainer]. - * @param worldFolder World folder. - * @param instanceContainer Instance container of the server. - */ - protected open suspend fun loadWorld(worldFolder: String, instanceContainer: InstanceContainer) { - val anvilWorld = File(workingDirectory, worldFolder) - if (!anvilWorld.isDirectory) { - throw FileSystemException( - anvilWorld, - null, - "World ${anvilWorld.absolutePath} does not exist or is not a directory" - ) - } - instanceContainer.chunkLoader = AnvilLoader(anvilWorld.toPath()) - } - /** * Load the configuration using the file or the default config file. * Will use the [HoconConfigurationReader] to load the configuration. From 83e30b9affd4acebe7987531825f1f3d96ee8709 Mon Sep 17 00:00:00 2001 From: Quentin Date: Thu, 20 Jul 2023 19:16:04 +0200 Subject: [PATCH 016/143] chore: Delete old code Deleting old code before import working code for paper. --- .../com/github/rushyverse/api/RushyServer.kt | 121 -------- .../rushyverse/api/command/CommandMessages.kt | 33 -- .../configuration/HoconConfigurationReader.kt | 51 ---- .../api/configuration/IConfiguration.kt | 79 ----- .../api/configuration/IConfigurationReader.kt | 84 ------ .../MinestomAsyncCoroutineDispatcher.kt | 23 -- .../coroutine/MinestomCoroutineDispatcher.kt | 30 -- .../MinestomSyncCoroutineDispatcher.kt | 23 -- .../github/rushyverse/api/entity/NPCEntity.kt | 96 ------ .../rushyverse/api/entity/PlayerNPCEntity.kt | 61 ---- .../rushyverse/api/extension/Acquirable.kt | 38 --- .../rushyverse/api/extension/CommandExt.kt | 66 ---- .../rushyverse/api/extension/ComponentExt.kt | 168 ----------- .../rushyverse/api/extension/EntityExt.kt | 72 ----- .../rushyverse/api/extension/InventoryExt.kt | 283 ------------------ .../rushyverse/api/extension/ItemStackExt.kt | 58 ---- .../rushyverse/api/extension/ListenerExt.kt | 29 -- .../rushyverse/api/extension/LocationExt.kt | 48 --- .../rushyverse/api/extension/MathExt.kt | 15 - .../rushyverse/api/extension/NumberExt.kt | 58 ---- .../rushyverse/api/extension/PropertyExt.kt | 22 -- .../rushyverse/api/extension/StringExt.kt | 104 ------- .../github/rushyverse/api/image/MapImage.kt | 254 ---------------- .../rushyverse/api/image/MapImageMath.kt | 173 ----------- .../exception/ImageNotLoadedException.kt | 16 - .../ItemFramesAlreadyExistException.kt | 6 - .../api/item/InventoryConditionSuspend.kt | 52 ---- .../rushyverse/api/item/ItemComparator.kt | 29 -- .../api/listener/EventListenerSuspend.kt | 34 --- .../api/listener/NPCListenerBuilder.kt | 40 --- .../rushyverse/api/position/AbstractArea.kt | 32 -- .../rushyverse/api/position/CubeArea.kt | 66 ---- .../rushyverse/api/position/CylinderArea.kt | 60 ---- .../github/rushyverse/api/position/IArea.kt | 19 -- .../rushyverse/api/position/IAreaLocatable.kt | 18 -- .../rushyverse/api/position/MultiArea.kt | 45 --- .../rushyverse/api/position/SphereArea.kt | 49 --- .../ItemFrameMetaOrientationSerializer.kt | 30 -- .../api/serializer/PosSerializer.kt | 112 ------- .../ResourceBundleNotRegisteredException.kt | 11 - .../ResourceBundleTranslationsProvider.kt | 85 ------ .../api/translation/SupportedLanguage.kt | 14 - .../api/translation/TranslationsProvider.kt | 43 --- .../github/rushyverse/api/utils/FileUtils.kt | 9 - 44 files changed, 2759 deletions(-) delete mode 100644 src/main/kotlin/com/github/rushyverse/api/RushyServer.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/command/CommandMessages.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReader.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/configuration/IConfiguration.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/configuration/IConfigurationReader.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/coroutine/MinestomAsyncCoroutineDispatcher.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/coroutine/MinestomCoroutineDispatcher.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/coroutine/MinestomSyncCoroutineDispatcher.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/entity/NPCEntity.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/entity/PlayerNPCEntity.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/Acquirable.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/CommandExt.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/ComponentExt.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/EntityExt.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/InventoryExt.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/ItemStackExt.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/ListenerExt.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/LocationExt.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/MathExt.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/NumberExt.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/PropertyExt.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/StringExt.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/image/MapImage.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/image/MapImageMath.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/image/exception/ImageNotLoadedException.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/image/exception/ItemFramesAlreadyExistException.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/item/InventoryConditionSuspend.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/item/ItemComparator.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/listener/EventListenerSuspend.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/listener/NPCListenerBuilder.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/position/AbstractArea.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/position/CubeArea.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/position/CylinderArea.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/position/IArea.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/position/IAreaLocatable.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/position/MultiArea.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/position/SphereArea.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/serializer/ItemFrameMetaOrientationSerializer.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/serializer/PosSerializer.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleNotRegisteredException.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationsProvider.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/translation/TranslationsProvider.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/utils/FileUtils.kt diff --git a/src/main/kotlin/com/github/rushyverse/api/RushyServer.kt b/src/main/kotlin/com/github/rushyverse/api/RushyServer.kt deleted file mode 100644 index e2a7390c..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/RushyServer.kt +++ /dev/null @@ -1,121 +0,0 @@ -package com.github.rushyverse.api - -import com.github.rushyverse.api.configuration.* -import com.github.rushyverse.api.translation.ResourceBundleTranslationsProvider -import com.github.rushyverse.api.translation.SupportedLanguage -import com.github.rushyverse.api.translation.TranslationsProvider -import com.github.rushyverse.api.translation.registerResourceBundleForSupportedLocales -import mu.KLogger -import mu.KotlinLogging -import org.bukkit.plugin.java.JavaPlugin -import java.util.* -import kotlin.reflect.KClass - -public val logger: KLogger = KotlinLogging.logger { } - -/** - * Abstract implementation of Minecraft server. - */ -public abstract class RushyServer : JavaPlugin() { - - public companion object API { - /** - * Name of the bundle for API. - */ - public const val BUNDLE_API: String = "api" - - /** - * Register the API commands. - */ - public fun registerCommands() { - // New commands here - } - } - - /** - * Start the minecraft server. - */ - public abstract suspend fun start() - - /** - * Initialize the server and start it using the given (or default) configuration. - * @param configurationPath Path of the configuration file in working directory. - * @param init Initialization function with the configuration loaded and the world container. - */ - protected suspend inline fun start( - configurationPath: String? = null, - init: (T) -> Unit - ) { - logger.info { "Loading configuration from $configurationPath" } - val config = loadConfiguration(configurationPath) - logger.info { "Configuration loaded" } - - val serverConfig = config.server - - applyVelocityConfiguration(serverConfig.velocity) - applyBungeeCordConfiguration(serverConfig.bungeeCord) - - init(config) - } - - /** - * Enable the [Velocity system][VelocityProxy] if the configuration [IVelocityConfiguration] is enabled. - * @param velocity Velocity configuration. - */ - protected open suspend fun applyVelocityConfiguration(velocity: IVelocityConfiguration) { - if (velocity.enabled) { - logger.info { "Enabling Velocity support" } - // TODO: Velocity configuration here - logger.info { "Velocity support enabled" } - } - } - - /** - * Enable the [BungeeCord system][BungeeCordProxy] if the configuration [IBungeeCordConfiguration] is enabled. - * @param bungeeCord BungeeCord configuration. - */ - protected open suspend fun applyBungeeCordConfiguration(bungeeCord: IBungeeCordConfiguration) { - if (bungeeCord.enabled) { - logger.info { "Enabling BungeeCord support" } - // TODO: BungeeCord configuration here - logger.info { "BungeeCord support enabled" } - } - } - - /** - * Load the configuration using the file or the default config file. - * Will use the [HoconConfigurationReader] to load the configuration. - * @param configFile Path of the configuration file. - * @return The configuration of the server. - */ - protected suspend inline fun loadConfiguration( - configFile: String? - ): T { - return loadConfiguration(T::class, configFile) - } - - /** - * Load the configuration using the file or the default config file. - * @param clazz Type of configuration class to load. - * @param configFile Path of the configuration file. - * @return The configuration of the server. - */ - protected open suspend fun loadConfiguration( - clazz: KClass, - configFile: String? - ): T { - val configurationFile = IConfigurationReader.getOrCreateConfigurationFile(configFile) - return HoconConfigurationReader().readConfigurationFile(clazz, configurationFile) - } - - /** - * Create a translation provider to provide translations for the [supported languages][SupportedLanguage]. - * @param bundles Bundles to load. - * @return New translation provider. - */ - protected open suspend fun createTranslationsProvider(bundles: Iterable): TranslationsProvider { - return ResourceBundleTranslationsProvider().apply { - bundles.forEach { registerResourceBundleForSupportedLocales(it, ResourceBundle::getBundle) } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/command/CommandMessages.kt b/src/main/kotlin/com/github/rushyverse/api/command/CommandMessages.kt deleted file mode 100644 index aab67ad2..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/command/CommandMessages.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.github.rushyverse.api.command - -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.format.NamedTextColor -import org.bukkit.command.CommandSender -import org.bukkit.entity.Player - -/** - * Utils class to send specific messages. - */ -public object CommandMessages { - - /** - * Send a message to the sender because no player was found in a previous process. - * @param sender Sender who will receive the message. - */ - public fun sendPlayerNotFoundMessage(sender: CommandSender) { - if (sender is Player) { - sender.sendMessage(Component.translatable("argument.entity.notfound.player", NamedTextColor.RED)) - } else { - sender.sendMessage(Component.text("No player was found", NamedTextColor.RED)) - } - } - - /** - * Send a message to the sender because he doesn't have the permission to execute the command. - * @param sender Sender who will receive the message. - */ - public fun sendMissingPermissionMessage(sender: CommandSender) { - sender.sendMessage(Component.translatable("commands.help.failed", NamedTextColor.RED)) - } - -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReader.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReader.kt deleted file mode 100644 index 2ce7d742..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReader.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.github.rushyverse.api.configuration - -import com.github.rushyverse.api.serializer.Pos -import com.github.rushyverse.api.serializer.PosSerializer -import com.typesafe.config.ConfigFactory -import kotlinx.serialization.KSerializer -import kotlinx.serialization.hocon.Hocon -import kotlinx.serialization.modules.SerializersModule -import kotlinx.serialization.serializer -import java.io.File -import kotlin.reflect.KClass -import kotlin.reflect.full.createType - -/** - * Read configuration from HOCON file. - * @receiver Configuration reader. - * @param configFile Configuration file to load. - * @return The configuration loaded from the given file. - */ -public inline fun HoconConfigurationReader.readConfigurationFile(configFile: File): T = - readConfigurationFile(hocon.serializersModule.serializer(), configFile) - -/** - * Read configuration from HOCON file. - * @property hocon [Hocon] configuration to use. - */ -public class HoconConfigurationReader(public val hocon: Hocon = hoconDefault) : IConfigurationReader { - - public companion object { - /** - * Default [Hocon] configuration using custom serializer. - * @see PosSerializer - */ - public val hoconDefault: Hocon = Hocon { - serializersModule = SerializersModule { - contextual(Pos::class, PosSerializer) - } - } - } - - override fun readConfigurationFile(clazz: KClass, configFile: File): T { - val serializer = hocon.serializersModule.serializer(clazz.createType()) - @Suppress("UNCHECKED_CAST") - return readConfigurationFile(serializer as KSerializer, configFile) - } - - override fun readConfigurationFile(serializer: KSerializer, configFile: File): T { - val config = ConfigFactory.parseFile(configFile) - return hocon.decodeFromConfig(serializer, config) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/IConfiguration.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/IConfiguration.kt deleted file mode 100644 index bd1d7bf3..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/configuration/IConfiguration.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.github.rushyverse.api.configuration - -import kotlinx.serialization.Serializable - -/** - * Configuration of the application. - * @property server Configuration of server. - */ -public interface IConfiguration { - - public val server: IServerConfiguration - -} - -/** - * Configuration about the minestom server. - * @property port Port of the server. - * @property world Path of the world to load. - * @property onlineMode `true` to enable the Mojang authentication. - * @property velocity Velocity configuration. - * @property bungeeCord BungeeCord configuration. - */ -public interface IServerConfiguration { - - public val port: Int - - public val world: String - - public val onlineMode: Boolean - - public val velocity: IVelocityConfiguration - - public val bungeeCord: IBungeeCordConfiguration -} - -/** - * Configuration to connect the server to the velocity proxy. - * @property enabled Whether the velocity support is enabled. - * @property secret Secret to verify if the client comes from the proxy. - */ -public interface IVelocityConfiguration { - - public val enabled: Boolean - - public val secret: String - -} - -/** - * Configuration to connect the server to the velocity proxy. - */ -@Serializable -public data class VelocityConfiguration( - override val enabled: Boolean, - override val secret: String -) : IVelocityConfiguration - - -/** - * Configuration to connect the server to the bungeeCord proxy. - * @property enabled Whether the server should connect to the bungeeCord proxy. - * @property secrets Secrets to verify if the client comes from the proxy. - */ -public interface IBungeeCordConfiguration { - - public val enabled: Boolean - - public val secrets: Set - -} - -/** - * Configuration to connect the server to the bungeeCord proxy. - */ -@Serializable -public data class BungeeCordConfiguration( - override val enabled: Boolean, - override val secrets: Set -) : IBungeeCordConfiguration \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/IConfigurationReader.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/IConfigurationReader.kt deleted file mode 100644 index 68d4aedc..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/configuration/IConfigurationReader.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.github.rushyverse.api.configuration - -import com.github.rushyverse.api.utils.workingDirectory -import kotlinx.serialization.KSerializer -import java.io.File -import java.io.FileNotFoundException -import kotlin.reflect.KClass - -/** - * Configuration reader. - */ -public interface IConfigurationReader { - - public companion object { - - /** - * Default name of the config file. - * This name is used to create the default config file when the user does not provide one. - */ - public const val DEFAULT_CONFIG_FILE_NAME: String = "server.conf" - - /** - * Get the configuration file from the given path. - * If the path is null, the default config file will be used. - * If the default config file does not exist, it will be created with the default configuration from resources folder. - * @param filePath Path of the configuration file. - * @return The configuration file that must be used to load application configuration. - */ - public fun getOrCreateConfigurationFile(filePath: String? = null): File { - if (filePath != null) { - val configFile = File(filePath) - if (!configFile.isFile) { - throw FileNotFoundException("Config file $filePath does not exist or is not a regular file") - } - return configFile - } - - return getOrCreateDefaultConfigurationFile(workingDirectory) - } - - /** - * Search for the default config file in the current directory. - * If the file does not exist, it will be created with the default configuration from resources folder. - * @param parent Parent directory of the default config file. - * @return The default config file. - */ - private fun getOrCreateDefaultConfigurationFile(parent: File): File = - File(parent, DEFAULT_CONFIG_FILE_NAME).apply { - if (exists()) { - return this - } - - val defaultConfiguration = - IConfigurationReader::class.java.classLoader.getResourceAsStream(DEFAULT_CONFIG_FILE_NAME) - ?: error("Unable to find default configuration file in server resources") - - defaultConfiguration.use { inputStream -> - if (!createNewFile()) { - throw FileSystemException(this, null, "Unable to create configuration file $absolutePath") - } - - outputStream().use { outputStream -> - inputStream.copyTo(outputStream) - } - } - } - } - - /** - * Load the configuration from the given file. - * @param clazz Type of configuration class to load. - * @param configFile Configuration file to load. - * @return The configuration loaded from the given file. - */ - public fun readConfigurationFile(clazz: KClass, configFile: File): T - - /** - * Load the configuration from the given file. - * @param serializer Serializer to deserialize the configuration to the given type. - * @param configFile Configuration file to load. - * @return The configuration loaded from the given file. - */ - public fun readConfigurationFile(serializer: KSerializer, configFile: File): T -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/coroutine/MinestomAsyncCoroutineDispatcher.kt b/src/main/kotlin/com/github/rushyverse/api/coroutine/MinestomAsyncCoroutineDispatcher.kt deleted file mode 100644 index 3a4ac576..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/coroutine/MinestomAsyncCoroutineDispatcher.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.github.rushyverse.api.coroutine - -import kotlinx.coroutines.Dispatchers -import net.minestom.server.MinecraftServer -import net.minestom.server.ServerProcess -import net.minestom.server.timer.ExecutionType - -/** - * Static instance of [MinestomAsyncCoroutineDispatcher]. - */ -internal val minestomAsyncDispatcher = MinestomAsyncCoroutineDispatcher(MinecraftServer.process()) - -/** - * @see [MinestomAsyncCoroutineDispatcher] - */ -public val Dispatchers.MinestomAsync: MinestomCoroutineDispatcher get() = minestomAsyncDispatcher - -/** - * Dispatcher to execute task in a [async][ExecutionType.ASYNC] context of the server. - * @property serverProcess Server's process - */ -public class MinestomAsyncCoroutineDispatcher(serverProcess: ServerProcess) : - MinestomCoroutineDispatcher(serverProcess, ExecutionType.ASYNC) \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/coroutine/MinestomCoroutineDispatcher.kt b/src/main/kotlin/com/github/rushyverse/api/coroutine/MinestomCoroutineDispatcher.kt deleted file mode 100644 index 1d031827..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/coroutine/MinestomCoroutineDispatcher.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.github.rushyverse.api.coroutine - -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob -import net.minestom.server.ServerProcess -import net.minestom.server.timer.ExecutionType -import kotlin.coroutines.CoroutineContext - -/** - * A coroutine dispatcher that uses the Minestom server thread. - * @property serverProcess The server process to use. - * @property type Determine if the execution should be [synchronous][ExecutionType.SYNC] or [asynchronous][ExecutionType.ASYNC]. - * @property scope Coroutine scope using the dispatcher and a [SupervisorJob]. - */ -public open class MinestomCoroutineDispatcher( - private val serverProcess: ServerProcess, - public val type: ExecutionType -) : CoroutineDispatcher() { - - public val scope: CoroutineScope by lazy { CoroutineScope(this + SupervisorJob()) } - - override fun dispatch(context: CoroutineContext, block: Runnable) { - if (!serverProcess.isAlive) { - return - } - - serverProcess.scheduler().scheduleNextProcess(block, type) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/coroutine/MinestomSyncCoroutineDispatcher.kt b/src/main/kotlin/com/github/rushyverse/api/coroutine/MinestomSyncCoroutineDispatcher.kt deleted file mode 100644 index 898fc369..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/coroutine/MinestomSyncCoroutineDispatcher.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.github.rushyverse.api.coroutine - -import kotlinx.coroutines.Dispatchers -import net.minestom.server.MinecraftServer -import net.minestom.server.ServerProcess -import net.minestom.server.timer.ExecutionType - -/** - * Static instance of [MinestomSyncCoroutineDispatcher]. - */ -internal val minestomSyncDispatcher = MinestomSyncCoroutineDispatcher(MinecraftServer.process()) - -/** - * @see [MinestomSyncCoroutineDispatcher] - */ -public val Dispatchers.MinestomSync: MinestomCoroutineDispatcher get() = minestomSyncDispatcher - -/** - * Dispatcher to execute task in a [sync][ExecutionType.SYNC] context of the server. - * @property serverProcess Server's process - */ -public class MinestomSyncCoroutineDispatcher(serverProcess: ServerProcess) : - MinestomCoroutineDispatcher(serverProcess, ExecutionType.SYNC) \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/entity/NPCEntity.kt b/src/main/kotlin/com/github/rushyverse/api/entity/NPCEntity.kt deleted file mode 100644 index 607d9c85..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/entity/NPCEntity.kt +++ /dev/null @@ -1,96 +0,0 @@ -package com.github.rushyverse.api.entity - -import com.extollit.gaming.ai.path.HydrazinePathFinder -import com.github.rushyverse.api.extension.sync -import com.github.rushyverse.api.position.IAreaLocatable -import net.minestom.server.coordinate.Pos -import net.minestom.server.entity.EntityType -import net.minestom.server.entity.LivingEntity -import net.minestom.server.entity.Player -import net.minestom.server.entity.pathfinding.NavigableEntity -import net.minestom.server.entity.pathfinding.Navigator -import net.minestom.server.event.player.PlayerEntityInteractEvent -import net.minestom.server.instance.Instance -import java.util.* -import java.util.concurrent.CompletableFuture - -/** - * A non-playable character. - * @property areaTrigger Area to know which players are near of the entity. - * @property navigator Navigator of the entity. - */ -public open class NPCEntity( - type: EntityType, - public var areaTrigger: IAreaLocatable? = null, - uuid: UUID = UUID.randomUUID() -) : LivingEntity(type, uuid), NavigableEntity { - - private val navigator: Navigator = Navigator(this) - - override fun getNavigator(): Navigator = navigator - - override fun update(time: Long) { - super.update(time) - navigator.tick() - updateAreaEntities(position, instance) - } - - /** - * If the [areaTrigger] is not null, update the entities in the area. - * @param position Position of the entity. - * @param instance Instance where is located the entity. - */ - private fun updateAreaEntities(position: Pos, instance: Instance) { - areaTrigger?.let { - it.position = position - it.instance = instance - val (enter, quit) = it.updateEntitiesInArea() - enter.forEach(this::onEnterArea) - quit.forEach(this::onLeaveArea) - } - } - - override fun setInstance(instance: Instance): CompletableFuture { - navigator.setPathFinder(HydrazinePathFinder(navigator.pathingEntity, instance.instanceSpace)) - return super.setInstance(instance) - } - - /** - * Look at the nearest player. - * Will retrieve the entities in [areaTrigger] and look at the nearest one by comparing the distance between the entity and the player. - * @throws IllegalStateException If the [areaTrigger] is null. - */ - public open fun lookNearestPlayer() { - val area = areaTrigger ?: throw IllegalStateException("An area detector must be set to use this method.") - val entities = area.entitiesInArea - if (entities.isEmpty()) return - - val npcPosition = position - val nearestEntity = entities.minByOrNull { - it.sync { position.distance(npcPosition) } - } ?: return - - lookAt(nearestEntity) - } - - /** - * Called when a player interact with the entity. - * @param event Event of the interaction. - */ - public open fun onInteract(event: PlayerEntityInteractEvent) { - } - - /** - * Called when a player enter the area of the entity. - * @param player Player who enter the area. - */ - public open fun onEnterArea(player: Player) { - } - - /** - * Called when a player leave the area of the entity. - * @param player Player who leave the area. - */ - public open fun onLeaveArea(player: Player) { - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/entity/PlayerNPCEntity.kt b/src/main/kotlin/com/github/rushyverse/api/entity/PlayerNPCEntity.kt deleted file mode 100644 index 83b2c0c7..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/entity/PlayerNPCEntity.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.github.rushyverse.api.entity - -import com.github.rushyverse.api.position.IAreaLocatable -import net.kyori.adventure.text.Component -import net.minestom.server.entity.EntityType -import net.minestom.server.entity.GameMode -import net.minestom.server.entity.Player -import net.minestom.server.network.packet.server.play.PlayerInfoPacket -import net.minestom.server.network.packet.server.play.PlayerInfoPacket.AddPlayer -import net.minestom.server.network.packet.server.play.PlayerInfoPacket.RemovePlayer -import java.util.* - -/** - * A non-player character that looks like a player. - * @property name Name of the NPC. - * @property properties Properties of the NPC. - * @property playerRemovePacket Remove player packet. - */ -public open class PlayerNPCEntity( - public val name: String, - public val properties: List = emptyList(), - areaTrigger: IAreaLocatable? = null, - uuid: UUID = UUID.randomUUID(), - public val inTabList: Boolean = false, -) : NPCEntity(EntityType.PLAYER, areaTrigger, uuid) { - - private val playerRemovePacket = PlayerInfoPacket( - PlayerInfoPacket.Action.REMOVE_PLAYER, - listOf(RemovePlayer(uuid)) - ) - - override fun updateNewViewer(player: Player) { - val connection = player.playerConnection - connection.sendPacket(createPlayerAddPacket()) - - if (!inTabList) { - scheduleNextTick { connection.sendPacket(playerRemovePacket) } - } - - super.updateNewViewer(player) - } - - /** - * Create a packet to add the NPC in the player view. - * @return A new packet. - */ - private fun createPlayerAddPacket() = PlayerInfoPacket( - PlayerInfoPacket.Action.ADD_PLAYER, - listOf( - AddPlayer( - uuid, - name, - properties, - GameMode.CREATIVE, - 0, - customName ?: Component.text(name), - null - ) - ) - ) -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/Acquirable.kt b/src/main/kotlin/com/github/rushyverse/api/extension/Acquirable.kt deleted file mode 100644 index b295b87f..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/extension/Acquirable.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.github.rushyverse.api.extension - -import net.minestom.server.entity.Entity -import net.minestom.server.thread.Acquirable -import net.minestom.server.thread.AcquirableCollection - -/** - * Get the [Acquirable] of the entity. - */ -public val E.acquirable: Acquirable get() = getAcquirable() - -/** - * Transform an iterable of entities into an [AcquirableCollection]. - * @receiver Iterable of entities. - * @return An [AcquirableCollection] of entities. - */ -public fun Iterable.toAcquirables(): AcquirableCollection { - return AcquirableCollection(map { it.getAcquirable() }) -} - -/** - * Transform an array of entities into an [AcquirableCollection]. - * @receiver Array of entities. - * @return An [AcquirableCollection] of entities. - */ -public fun Array.toAcquirables(): AcquirableCollection { - return AcquirableCollection(map { it.getAcquirable() }) -} - -/** - * Transform a sequence of entities into an [AcquirableCollection]. - * @receiver Sequence of entities. - * @return An [AcquirableCollection] of entities. - */ -public fun Sequence.toAcquirables(): AcquirableCollection { - return AcquirableCollection(map { it.acquirable }.toList()) -} - diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/CommandExt.kt b/src/main/kotlin/com/github/rushyverse/api/extension/CommandExt.kt deleted file mode 100644 index 89d8ba95..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/extension/CommandExt.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.github.rushyverse.api.extension - -import com.github.rushyverse.api.coroutine.MinestomSync -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import net.minestom.server.command.CommandSender -import net.minestom.server.command.builder.Command -import net.minestom.server.command.builder.CommandContext -import net.minestom.server.command.builder.CommandSyntax -import net.minestom.server.command.builder.arguments.Argument - -/** - * Allows to set the default executor command in a coroutine scope. - * @see [Command.setDefaultExecutor] - * @receiver Command where the default executor will be set. - * @param executor Executor to process the command in a suspendable context. - * @param coroutineScope Coroutine scope where the default executor will be executed. - */ -public inline fun Command.setDefaultExecutorSuspend( - coroutineScope: CoroutineScope = Dispatchers.MinestomSync.scope, - crossinline executor: suspend (sender: CommandSender, context: CommandContext) -> Unit -): Unit = setDefaultExecutor { sender, context -> - coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) { - executor(sender, context) - } -} - -/** - * Allows to add a syntax to a command in a coroutine scope. - * @see [Command.addSyntax] - * @receiver Command where the syntax will be added. - * @param executor Executor to process the command in a suspendable context. - * @param arguments Arguments of the syntax. - * @param coroutineScope Coroutine scope where the syntax will be executed. - */ -public inline fun Command.addSyntaxSuspend( - crossinline executor: suspend (sender: CommandSender, context: CommandContext) -> Unit, - vararg arguments: Argument<*>, - coroutineScope: CoroutineScope = Dispatchers.MinestomSync.scope -): MutableCollection = addSyntax({ sender, context -> - coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) { - executor(sender, context) - } -}, *arguments) - -/** - * Allows to add a conditional syntax to a command in a coroutine scope. - * @see [Command.addConditionalSyntax] - * @receiver Command where the syntax will be added. - * @param condition Condition to check before executing the syntax. - * @param executor Executor to process the command in a suspendable context. - * @param arguments Arguments of the syntax. - * @param coroutineScope Coroutine scope where the syntax will be executed. - */ -public inline fun Command.addConditionalSyntaxSuspend( - noinline condition: (sender: CommandSender, commandString: String?) -> Boolean, - crossinline executor: suspend (sender: CommandSender, context: CommandContext) -> Unit, - vararg arguments: Argument<*>, - coroutineScope: CoroutineScope = Dispatchers.MinestomSync.scope -): MutableCollection = addConditionalSyntax(condition, { sender, context -> - coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) { - executor(sender, context) - } -}, *arguments) \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/ComponentExt.kt b/src/main/kotlin/com/github/rushyverse/api/extension/ComponentExt.kt deleted file mode 100644 index 03c1e011..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/extension/ComponentExt.kt +++ /dev/null @@ -1,168 +0,0 @@ -package com.github.rushyverse.api.extension - -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.TextComponent -import net.kyori.adventure.text.format.TextDecoration -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer - -/** - * Transform a component into a legacy text. - * @receiver Component to transform. - * @return The legacy text. - */ -public fun Component.toText(): String = LegacyComponentSerializer.legacySection().serialize(this) - -/** - * Add the [bold][TextDecoration.BOLD] decoration to the component. - * @receiver Component to transform. - * @return The same component. - */ -public fun Component.withBold(): Component = this.decoration(TextDecoration.BOLD, TextDecoration.State.TRUE) - -/** - * Remove the [bold][TextDecoration.BOLD] decoration to the component. - * @receiver Component to transform. - * @return The same component. - */ -public fun Component.withoutBold(): Component = this.decoration(TextDecoration.BOLD, TextDecoration.State.FALSE) - -/** - * Set as [not set][TextDecoration.State.NOT_SET] the [bold][TextDecoration.BOLD] decoration of the component. - * @receiver Component to transform. - * @return The same component. - */ -public fun Component.undefineBold(): Component = this.decoration(TextDecoration.BOLD, TextDecoration.State.NOT_SET) - -/** - * Add the [italic][TextDecoration.ITALIC] decoration to the component. - * @receiver Component to transform. - * @return The same component. - */ -public fun Component.withItalic(): Component = this.decoration(TextDecoration.ITALIC, TextDecoration.State.TRUE) - -/** - * Remove the [italic][TextDecoration.ITALIC] decoration to the component. - * @receiver Component to transform. - * @return The same component. - */ -public fun Component.withoutItalic(): Component = this.decoration(TextDecoration.ITALIC, TextDecoration.State.FALSE) - -/** - * Set as [not set][TextDecoration.State.NOT_SET] the [italic][TextDecoration.ITALIC] decoration of the component. - * @receiver Component to transform. - * @return The same component. - */ -public fun Component.undefineItalic(): Component = this.decoration(TextDecoration.ITALIC, TextDecoration.State.NOT_SET) - -/** - * Add the [underlined][TextDecoration.UNDERLINED] decoration to the component. - * @receiver Component to transform. - * @return The same component. - */ -public fun Component.withUnderlined(): Component = this.decoration(TextDecoration.UNDERLINED, TextDecoration.State.TRUE) - -/** - * Remove the [underlined][TextDecoration.UNDERLINED] decoration to the component. - * @receiver Component to transform. - * @return The same component. - */ -public fun Component.withoutUnderlined(): Component = - this.decoration(TextDecoration.UNDERLINED, TextDecoration.State.FALSE) - -/** - * Set as [not set][TextDecoration.State.NOT_SET] the [underlined][TextDecoration.UNDERLINED] decoration of the component. - * @receiver Component to transform. - * @return The same component. - */ -public fun Component.undefineUnderlined(): Component = - this.decoration(TextDecoration.UNDERLINED, TextDecoration.State.NOT_SET) - -/** - * Add the [strikethrough][TextDecoration.STRIKETHROUGH] decoration to the component. - * @receiver Component to transform. - * @return The same component. - */ -public fun Component.withStrikethrough(): Component = - this.decoration(TextDecoration.STRIKETHROUGH, TextDecoration.State.TRUE) - -/** - * Remove the [strikethrough][TextDecoration.STRIKETHROUGH] decoration to the component. - * @receiver Component to transform. - * @return The same component. - */ -public fun Component.withoutStrikethrough(): Component = - this.decoration(TextDecoration.STRIKETHROUGH, TextDecoration.State.FALSE) - -/** - * Set as [not set][TextDecoration.State.NOT_SET] the [strikethrough][TextDecoration.STRIKETHROUGH] decoration of the component. - * @receiver Component to transform. - * @return The same component. - */ -public fun Component.undefineStrikethrough(): Component = - this.decoration(TextDecoration.STRIKETHROUGH, TextDecoration.State.NOT_SET) - -/** - * Add the [obfuscated][TextDecoration.OBFUSCATED] decoration to the component. - * @receiver Component to transform. - * @return The same component. - */ -public fun Component.withObfuscated(): Component = this.decoration(TextDecoration.OBFUSCATED, TextDecoration.State.TRUE) - -/** - * Remove the [obfuscated][TextDecoration.OBFUSCATED] decoration to the component. - * @receiver Component to transform. - * @return The same component. - */ -public fun Component.withoutObfuscated(): Component = - this.decoration(TextDecoration.OBFUSCATED, TextDecoration.State.FALSE) - -/** - * Set as [not set][TextDecoration.State.NOT_SET] the [obfuscated][TextDecoration.OBFUSCATED] decoration of the component. - * @receiver Component to transform. - * @return The same component. - */ -public fun Component.undefineObfuscated(): Component = - this.decoration(TextDecoration.OBFUSCATED, TextDecoration.State.NOT_SET) - -/** - * Add all decorations to the component. - * @receiver Component to transform. - * @return The same component. - */ -public fun Component.withDecorations(): Component = - withBold().withItalic().withUnderlined().withStrikethrough().withObfuscated() - -/** - * Remove all decorations from the component. - * @receiver Component to transform. - * @return The same component. - */ -public fun Component.withoutDecorations(): Component = - withoutBold().withoutItalic().withoutUnderlined().withoutStrikethrough().withoutObfuscated() - -/** - * Set as [not set][TextDecoration.State.NOT_SET] all decorations of the component. - * @receiver Component to transform. - * @return The same component. - */ -public fun Component.undefineDecorations(): Component = - undefineBold().undefineItalic().undefineUnderlined().undefineStrikethrough().undefineObfuscated() - -/** - * Create a new component using the string content. - * @receiver String to transform. - * @param extractUrls If true, will extract urls from the string and apply a clickable effect on them. - * @param extractColors If true, will extract colors from the string and apply them to the component. - * @param colorChar The character used to define a color. - * @return A new text component. - */ -public fun String.toComponent( - extractUrls: Boolean = false, - extractColors: Boolean = true, - colorChar: Char = LegacyComponentSerializer.AMPERSAND_CHAR, -): TextComponent = LegacyComponentSerializer.builder().apply { - if (extractUrls) extractUrls() - if (extractColors) character(colorChar) -} - .build() - .deserialize(this) \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/EntityExt.kt b/src/main/kotlin/com/github/rushyverse/api/extension/EntityExt.kt deleted file mode 100644 index 8ea09108..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/extension/EntityExt.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.github.rushyverse.api.extension - -import com.github.rushyverse.api.coroutine.MinestomAsync -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import net.minestom.server.entity.Entity -import net.minestom.server.event.EventListener -import net.minestom.server.event.trait.EntityEvent -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract - -/** - * Async version of [sync]. - * @receiver Entity to lock. - * @param block The callback to execute once the element has been safely acquired. - * @return The result of the callback in a [Deferred] object. - */ -public inline fun E.async( - coroutineScope: CoroutineScope = Dispatchers.MinestomAsync.scope, - crossinline block: suspend E.() -> T -): Deferred = coroutineScope.async { - val acquirable = acquirable.lock() - try { - block(acquirable.get()) - } finally { - acquirable.unlock() - } -} - -/** - * Locks the acquirable element, execute {@code consumer} synchronously and unlock the thread. - * Free if the element is already present in the current thread, blocking otherwise. - * @receiver Entity to lock. - * @param block The callback to execute once the element has been safely acquired. - * @return The result of the callback. - */ -public inline fun E.sync(block: E.() -> T): T { - contract { - callsInPlace(block, InvocationKind.EXACTLY_ONCE) - } - - val acquirable = acquirable.lock() - try { - return block(acquirable.get()) - } finally { - acquirable.unlock() - } -} - -/** - * Registers an event listener for this entity. - * @param block Handler of event. - * @return The registered event listener. - */ -public inline fun Entity.onEvent( - crossinline block: Entity.(T) -> EventListener.Result -): EventListener { - val listener = object : EventListener { - - override fun eventType(): Class { - return T::class.java - } - - override fun run(event: T): EventListener.Result { - return block(event) - } - } - eventNode().addListener(listener) - return listener -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/InventoryExt.kt b/src/main/kotlin/com/github/rushyverse/api/extension/InventoryExt.kt deleted file mode 100644 index 37068e0f..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/extension/InventoryExt.kt +++ /dev/null @@ -1,283 +0,0 @@ -package com.github.rushyverse.api.extension - -import com.github.rushyverse.api.coroutine.MinestomSync -import com.github.rushyverse.api.item.InventoryConditionSuspend -import com.github.rushyverse.api.item.ItemComparator -import com.github.rushyverse.api.item.asNative -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.format.NamedTextColor -import net.kyori.adventure.text.format.TextDecoration -import net.minestom.server.inventory.AbstractInventory -import net.minestom.server.inventory.Inventory -import net.minestom.server.inventory.condition.InventoryCondition -import net.minestom.server.item.ItemStack -import net.minestom.server.item.Material - -/** - * Range of slots. - */ -public val AbstractInventory.slots: IntRange - get() = this.itemStacks.indices - -/** - * Add a new suspend inventory condition to the inventory. - * @receiver Inventory where the condition will be added. - * @param coroutineScope Scope to launch action when a new event is received. - * @param inventoryConditionSuspend Inventory condition with a suspendable execution. - * @return A native [InventoryCondition] added to the inventory. - */ -public fun AbstractInventory.addInventoryConditionSuspend( - coroutineScope: CoroutineScope = Dispatchers.MinestomSync.scope, - inventoryConditionSuspend: InventoryConditionSuspend -): InventoryCondition { - return inventoryConditionSuspend.asNative(coroutineScope).apply { addInventoryCondition(this) } -} - -/** - * Remove the condition of interaction with the inventory. - * @receiver Inventory. - * @param condition Condition to remove. - * @return `true` if the condition was removed, `false` otherwise. - */ -public fun AbstractInventory.removeCondition(condition: InventoryCondition): Boolean = - inventoryConditions.remove(condition) - -/** - * Lock the position of all items in the inventory. - * If a player tries to move an item, the item will not move. - * @receiver Inventory. - * @return Condition of interaction with the inventory. - */ -public fun AbstractInventory.lockItemPositions(): InventoryCondition { - val condition = InventoryCondition { _, _, _, result -> - result.isCancel = true - } - addInventoryCondition(condition) - return condition -} - -/** - * Handle the click event on a specific slot in coroutine context. - * @receiver Inventory where the handler will be used. - * @param slot Slot that should be clicked. - * @param coroutineScope Coroutine scope where the handler will be called. - * @param handler Handler that will be called when the slot is clicked. - * @return Condition of interaction with the inventory. - */ -public fun AbstractInventory.registerClickEventOnSlotSuspend( - slot: Int, - coroutineScope: CoroutineScope = Dispatchers.MinestomSync.scope, - handler: InventoryConditionSuspend -): InventoryCondition = registerClickEventOnSlot(slot, handler.asNative(coroutineScope)) - -/** - * Handle the click event on a specific slot. - * @receiver Inventory where the handler will be used. - * @param slot Slot that should be clicked. - * @param handler Handler that will be called when the slot is clicked. - * @return Condition of interaction with the inventory. - */ -public fun AbstractInventory.registerClickEventOnSlot(slot: Int, handler: InventoryCondition): InventoryCondition { - val condition = InventoryCondition { player, clickedSlot, clickType, result -> - if (clickedSlot == slot) { - handler.accept(player, clickedSlot, clickType, result) - } - } - addInventoryCondition(condition) - return condition -} - -/** - * Add a handler when the player click on the item. - * @receiver Inventory. - * @param item Item that should be clicked. - * @param identifier Allows to identify if an item is equivalent to another. - * @param coroutineScope Coroutine scope where the handler will be called. - * @param handler Handler that will be called when the item is clicked. - */ -public fun AbstractInventory.registerClickEventOnItemSuspend( - item: ItemStack, - identifier: ItemComparator = ItemComparator.EQUALS, - coroutineScope: CoroutineScope = Dispatchers.MinestomSync.scope, - handler: InventoryConditionSuspend -): InventoryCondition = registerClickEventOnItem(item, identifier, handler.asNative(coroutineScope)) - -/** - * Add a handler when the player click on the item. - * @receiver Inventory. - * @param item Item that should be clicked. - * @param identifier Allows to identify if an item is equivalent to another. - * @param handler Handler that will be called when the item is clicked. - */ -public fun AbstractInventory.registerClickEventOnItem( - item: ItemStack, - identifier: ItemComparator = ItemComparator.EQUALS, - handler: InventoryCondition -): InventoryCondition { - val condition = InventoryCondition { player, clickedSlot, clickType, result -> - if (identifier.areSame(item, result.clickedItem)) { - handler.accept(player, clickedSlot, clickType, result) - } - } - addInventoryCondition(condition) - return condition -} - -/** - * Add the item to the inventory on the specific slot and add a handler when the player click on the item. - * @receiver Inventory. - * @param slot Slot where the item will be added. - * @param item Item that will be added. - * @param identifier Allows to identify if an item is equivalent to another. - * @param coroutineScope Coroutine scope where the handler will be called. - * @param handler Handler that will be called when the item is clicked. - */ -public fun AbstractInventory.setItemStackSuspend( - slot: Int, - item: ItemStack, - identifier: ItemComparator = ItemComparator.EQUALS, - coroutineScope: CoroutineScope = Dispatchers.MinestomSync.scope, - handler: InventoryConditionSuspend -): InventoryCondition = setItemStack(slot, item, identifier, handler.asNative(coroutineScope)) - -/** - * Add the item to the inventory on the specific slot and add a handler when the player click on the item. - * @receiver Inventory. - * @param slot Slot where the item will be added. - * @param item Item that will be added. - * @param identifier Allows to identify if an item is equivalent to another. - * @param handler Handler that will be called when the item is clicked. - */ -public fun AbstractInventory.setItemStack( - slot: Int, - item: ItemStack, - identifier: ItemComparator = ItemComparator.EQUALS, - handler: InventoryCondition, -): InventoryCondition { - this.setItemStack(slot, item) - return registerClickEventOnItem(item, identifier, handler) -} - -/** - * Know if the slot has no item. - * @receiver Inventory checked. - * @param slot Slot checked. - * @return `true` if the slot is empty, `false` otherwise. - */ -public fun AbstractInventory.slotIsEmpty(slot: Int): Boolean = getItemStack(slot).isAir - -/** - * Get the first available slot. - * @receiver Inventory. - * @return The slot number or `-1` if there is no available slot. - */ -public fun AbstractInventory.firstAvailableSlot(): Int = this.itemStacks.indexOfFirst { it.isAir } - -/** - * Add the item on the first available slot and add a handler when the player click on the item. - * If there is no available slot, the item will not be added. - * @receiver Inventory. - * @param item Item that should be added. - * @param identifier Allows to identify if an item is equivalent to another. - * @param coroutineScope Coroutine scope where the handler will be called. - * @param handler Handler that will be called when the item is clicked. - * @return The created handler or `null` if there is no available slot. - */ -public fun AbstractInventory.addItemStackSuspend( - item: ItemStack, - identifier: ItemComparator = ItemComparator.EQUALS, - coroutineScope: CoroutineScope = Dispatchers.MinestomSync.scope, - handler: InventoryConditionSuspend -): InventoryCondition? = addItemStack(item, identifier, handler.asNative(coroutineScope)) - -/** - * Add the item on the first available slot and add a handler when the player click on the item. - * If there is no available slot, the item will not be added. - * @receiver Inventory. - * @param item Item that should be added. - * @param identifier Allows to identify if an item is equivalent to another. - * @param handler Handler that will be called when the item is clicked. - * @return The created handler or `null` if there is no available slot. - */ -public fun AbstractInventory.addItemStack( - item: ItemStack, - identifier: ItemComparator = ItemComparator.EQUALS, - handler: InventoryCondition, -): InventoryCondition? { - return if (addItemStack(item)) { - registerClickEventOnItem(item, identifier, handler) - } else null -} - -/** - * Set the item on the specific slot to go back to the previous inventory. - * @receiver Inventory where the item will be added. - * @param slot Slot where the item will be set. - * @param backInventory Redirection inventory. - * @return Condition of interaction with the inventory. - */ -public fun AbstractInventory.setPreviousButton(slot: Int, backInventory: Inventory): InventoryCondition { - return setItemChangeInventory(slot, backInventory, "< ") -} - -/** - * Set the item on the specific slot to go to the next inventory. - * @receiver Inventory where the item will be added. - * @param slot Slot where the item will be set. - * @param nextInventory Redirection inventory. - * @return Condition of interaction with the inventory. - */ -public fun AbstractInventory.setNextButton(slot: Int, nextInventory: Inventory): InventoryCondition { - return setItemChangeInventory(slot, nextInventory, "> ") -} - -/** - * Set the item on the specific slot to go to the another inventory. - * @receiver Inventory where the item will be added. - * @param slot Slot where the item will be set. - * @param otherInventory Redirection inventory. - * @param textItem Text of the item. - * @return Condition of interaction with the inventory. - */ -public fun AbstractInventory.setItemChangeInventory( - slot: Int, - otherInventory: Inventory, - textItem: String -): InventoryCondition { - val inventoryTitle = otherInventory.title - .color(NamedTextColor.GRAY) - .decoration(TextDecoration.ITALIC, true) - .decoration(TextDecoration.BOLD, false) - - val item = ItemStack.of(Material.ARROW) - .withDisplayName( - Component.text(textItem) - .color(NamedTextColor.GOLD) - .decoration(TextDecoration.BOLD, true) - .append(inventoryTitle) - ) - - return setItemStack(slot, item) { player, _, _, result -> - result.isCancel = true - player.openInventory(otherInventory) - } -} - -/** - * Add a close button to the inventory and a handler to close the inventory. - * The item will override the item on the specific slot. - * @receiver Inventory where the button will be added. - * @param slot Slot where the item will be added. - * @return The created handler. - */ -public fun AbstractInventory.setCloseButton(slot: Int): InventoryCondition { - val closeItem = ItemStack.of(Material.BARRIER) - .withDisplayName(Component.text("❌").color(NamedTextColor.RED)) - - return setItemStack(slot, closeItem) { player, _, _, result -> - result.isCancel = true - player.closeInventory() - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/ItemStackExt.kt b/src/main/kotlin/com/github/rushyverse/api/extension/ItemStackExt.kt deleted file mode 100644 index a9ec3241..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/extension/ItemStackExt.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.github.rushyverse.api.extension - -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.TextComponent -import net.kyori.adventure.text.format.NamedTextColor -import org.bukkit.inventory.ItemStack - -/** - * Format the string [loreString] using [toFormattedLoreSequence] and transform it into a [TextComponent] using [toLore]. - * @receiver builder to transform. - * @param loreString String that will be formatted and transform it to lore components. - * @param lineLength The maximum length of each line. - * @param transform A function that will be applied to each component. - * @return The same builder with the lore modified. - */ -public inline fun ItemStack.formattedLore( - loreString: String, - lineLength: Int = DEFAULT_LORE_LINE_LENGTH, - crossinline transform: TextComponent.Builder.() -> Unit = { - color(NamedTextColor.GRAY) - } -): ItemStack { - val meta = this.itemMeta - meta.lore(loreString.toFormattedLoreSequence(lineLength).toLore(transform)) - this.itemMeta = meta - return this -} - -/** - * Format the string [loreString] using [toFormattedLoreSequence] and transform it into a [TextComponent] using [toLore]. - * @receiver ItemStack to transform. - * @param loreString String that will be formatted and transform it to lore components. - * @param lineLength The maximum length of each line. - * @param transform A function that will be applied to each component. - * @return The same ItemStack with the lore modified. - */ -public inline fun ItemStack.withFormattedLore( - loreString: String, - lineLength: Int = DEFAULT_LORE_LINE_LENGTH, - crossinline transform: TextComponent.Builder.() -> Unit = { - color(NamedTextColor.GRAY) - } -): ItemStack { - val meta = this.itemMeta - meta.lore(loreString.toFormattedLoreSequence(lineLength).toLore(transform)) - this.itemMeta = meta - return this -} - -/** - * Define an unique component as lore. - * @receiver ItemStack. - * @param lore Lore to set. - * @return The same item. - */ -public fun ItemStack.withLore(lore: Component): ItemStack = apply { - itemMeta = itemMeta.apply { lore(listOf(lore)) } -} diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/ListenerExt.kt b/src/main/kotlin/com/github/rushyverse/api/extension/ListenerExt.kt deleted file mode 100644 index d3e0ac5b..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/extension/ListenerExt.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.github.rushyverse.api.extension - -import com.github.rushyverse.api.coroutine.MinestomSync -import com.github.rushyverse.api.listener.EventListenerSuspend -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import net.minestom.server.event.Event -import net.minestom.server.event.EventNode - -/** - * Allows to handle event in a coroutine context. - * @receiver Event node to register the event listener to. - * @param coroutineScope Coroutine scope where the event will be handled. - * @param handle Handler of the event. - */ -public inline fun EventNode.addListenerSuspend( - coroutineScope: CoroutineScope = Dispatchers.MinestomSync.scope, - crossinline handle: suspend (E) -> Unit -) { - addListener(object : EventListenerSuspend(coroutineScope) { - override suspend fun runSuspend(event: E) { - handle(event) - } - - override fun eventType(): Class { - return E::class.java - } - }) -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/LocationExt.kt b/src/main/kotlin/com/github/rushyverse/api/extension/LocationExt.kt deleted file mode 100644 index 750d9022..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/extension/LocationExt.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.github.rushyverse.api.extension - -import com.github.rushyverse.api.serializer.Pos -import org.bukkit.Location -import kotlin.math.pow -import kotlin.math.sqrt - -/** - * Returns the center location between two points. - * @receiver The first location. - * @param other The second location. - * @return The center location between the two points. - */ -public fun Location.centerRelative(other: Location): Location = - add(other).multiply(0.5) - -/** - * Returns whether the given [Location] is in the cube defined by the two given [Location]s. - * @receiver The location to check. - * @param min The minimum location of the cube. - * @param max The maximum location of the cube. - * @return `true` if the location is in the cube, `false` otherwise. - */ -public fun Location.isInCube(min: Location, max: Location): Boolean { - return x in min.x..max.x && y in min.y..max.y && z in min.z..max.z -} - -/** - * Returns whether the given [Location] is in the cylinder defined by the given [locationCylinder], [radius] and height defined by [limitY]. - * @receiver The location to check. - * @param locationCylinder The location of the cylinder. - * @param radius The radius of the cylinder. - * @param limitY The height of the cylinder. - * @return `true` if the location is in the cylinder, `false` otherwise. - */ -public fun Location.isInCylinder(locationCylinder: Location, radius: Double, limitY: ClosedRange): Boolean { - val distance = sqrt((x - locationCylinder.x).pow(2.0) + (z - locationCylinder.z).pow(2.0)) - return distance <= radius && y in limitY -} - -/** - * Converts this [Location] object to a [Pos] object. - * This function allows serializing coordinates without taking the world into account. - * - * @receiver The [Location] to convert. - * @return A new [Pos] object with the same position and rotation as this [Location] object. - */ -public fun Location.toPos(): Pos = Pos(x, y, z, yaw, pitch) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/MathExt.kt b/src/main/kotlin/com/github/rushyverse/api/extension/MathExt.kt deleted file mode 100644 index 5a75ed35..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/extension/MathExt.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.github.rushyverse.api.extension - -/** - * Get the lowest and highest value in a specific index. - * The lowest value is placed at the index 0 and highest at the index 1. - * - * @param a First value. - * @param b Second value. - * @return Both values with a defined order. - */ -public fun > minMaxOf(a: T, b: T): Pair = if (a <= b) { - a to b -} else { - b to a -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/NumberExt.kt b/src/main/kotlin/com/github/rushyverse/api/extension/NumberExt.kt deleted file mode 100644 index cf6a41c0..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/extension/NumberExt.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.github.rushyverse.api.extension - -/** - * The values of roman number linked to their roman numerals. - * The values are ordered from the largest to the smallest. - */ -public val ROMAN_NUMERALS_VALUES: Map = mapOf( - 1000 to "M", - 900 to "CM", - 500 to "D", - 400 to "CD", - 100 to "C", - 90 to "XC", - 50 to "L", - 40 to "XL", - 10 to "X", - 9 to "IX", - 5 to "V", - 4 to "IV", - 1 to "I" -) - -/** - * The roman numerals. - * The numerals are ordered from the largest to the smallest. - * @see ROMAN_VALUES - */ -public val ROMAN_NUMERALS: Array = ROMAN_NUMERALS_VALUES.values.toTypedArray() - -/** - * The values of roman number. - * The values are ordered from the largest to the smallest. - * @see ROMAN_NUMERALS - */ -public val ROMAN_VALUES: IntArray = ROMAN_NUMERALS_VALUES.keys.toIntArray() - -/** - * Convert a number to roman numerals. - * @receiver Int between 1 and 3999. - * @return A string of roman numerals. - */ -public fun Int.toRomanNumerals(): String { - require(this > 0) { "Number must be positive" } - require(this < 4000) { "Number must be less than 4000" } - - var remaining = this - var i = 0 - return buildString { - while(remaining > 0) { - val romanValue = ROMAN_VALUES[i] - repeat(remaining / romanValue) { - append(ROMAN_NUMERALS[i]) - remaining -= romanValue - } - i++ - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/PropertyExt.kt b/src/main/kotlin/com/github/rushyverse/api/extension/PropertyExt.kt deleted file mode 100644 index 177e9a0f..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/extension/PropertyExt.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.github.rushyverse.api.extension - -import net.minestom.server.network.packet.server.play.PlayerInfoPacket.AddPlayer -import net.minestom.server.network.player.GameProfile - -/** - * Create a [property][GameProfile.Property] for textures. - * @param textures Textures string. - * @param signature Texture signature. - * @return A property for textures. - */ -public fun GameProfileTextureProperty(textures: String, signature: String): GameProfile.Property = - GameProfile.Property("textures", textures, signature) - -/** - * Create a [property][AddPlayer.Property] for textures. - * @param textures Textures string. - * @param signature Texture signature. - * @return A property for textures. - */ -public fun AddPlayerTextureProperty(textures: String, signature: String): AddPlayer.Property = - AddPlayer.Property("textures", textures, signature) \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/StringExt.kt b/src/main/kotlin/com/github/rushyverse/api/extension/StringExt.kt deleted file mode 100644 index 177cdf15..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/extension/StringExt.kt +++ /dev/null @@ -1,104 +0,0 @@ -package com.github.rushyverse.api.extension - -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.TextComponent -import net.kyori.adventure.text.format.NamedTextColor -import net.kyori.adventure.text.minimessage.MiniMessage -import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver - -/** - * Default max line for a lore line. - * This value is defined by looking with the default Minecraft size application. - */ -public const val DEFAULT_LORE_LINE_LENGTH: Int = 30 - -/** - * Transform a sequence of strings to a component. - * Each string will be transformed into a component and then joined together by a new line. - * @receiver The sequence of strings to transform. - * @param transform The transform function to apply to each string. - * @return A component that contains all the strings. - */ -public inline fun Sequence.toLore( - crossinline transform: TextComponent.Builder.() -> Unit = { - color(NamedTextColor.GRAY) - } -): List { - return map { Component.text().content(it).apply(transform).build() }.toList() -} - -/** - * Transform a collection of strings to a component. - * Each string will be transformed into a component and then joined together by a new line. - * @receiver The collection of strings to transform. - * @param transform A function that will be applied to each component. - * @return A component that contains all the strings. - */ -public inline fun Collection.toLore( - crossinline transform: TextComponent.Builder.() -> Unit = { - color(NamedTextColor.GRAY) - } -): List { - if (isEmpty()) return emptyList() - return map { Component.text().content(it).apply(transform).build() } -} - -/** - * Transform a string into a list of string by cutting it. - * If the string is too large and doesn't have any space, it will be cut each [lineLength] characters and a '-' will be added. - * If the string contains a space, it will be cut at the space. - * @receiver String to transform. - * @param lineLength Max size of each string. - * @return A list with strings with length less or equals to [lineLength]. - */ -public fun String.toFormattedLore(lineLength: Int = DEFAULT_LORE_LINE_LENGTH): List { - return toFormattedLoreSequence(lineLength).toList() -} - -/** - * Transform a string into a sequence of string by cutting. - * If the string is too large and doesn't have any space, it will be cut each [lineLength] characters and a '-' will be added. - * If the string contains a space, it will be cut at the space. - * @receiver String to transform. - * @param lineLength Max size of each string. - * @return A sequence with strings with length less or equals to [lineLength]. - */ -public fun String.toFormattedLoreSequence(lineLength: Int = DEFAULT_LORE_LINE_LENGTH): Sequence { - if (isEmpty()) return emptySequence() - if (length <= lineLength) return sequenceOf(this) - - var index = 0 - return sequence { - while (index < length) { - val nextIndex = index + lineLength - if (nextIndex >= length) { - yield(substring(index)) - break - } - - val substringToNextIndex = substring(index, nextIndex) - val substringBeforeLastSpace = substringToNextIndex.substringBeforeLast(' ') - val nextChar = get(index + substringBeforeLastSpace.length) - - index += if (nextChar.isWhitespace()) { - yield(substringBeforeLastSpace) - // +1 to skip the space - substringBeforeLastSpace.length + 1 - } else { - yield(substringToNextIndex.dropLast(1) + '-') - substringToNextIndex.lastIndex - } - } - } -} - -/** - * Transforms a string into a component using MiniMessage. - * Will set the color according to the tag in the string. - * The [tagResolver] will be used to resolve the custom tags and replace values. - * @receiver The string used to create the component. - * @param tagResolver The tag resolver used to resolve the custom tags. - * @return The component created from the string. - */ -public fun String.asMiniComponent(vararg tagResolver: TagResolver): Component = - MiniMessage.miniMessage().deserialize(this, *tagResolver) \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/image/MapImage.kt b/src/main/kotlin/com/github/rushyverse/api/image/MapImage.kt deleted file mode 100644 index d375335b..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/image/MapImage.kt +++ /dev/null @@ -1,254 +0,0 @@ -package com.github.rushyverse.api.image - -import com.github.rushyverse.api.image.exception.ImageAlreadyLoadedException -import com.github.rushyverse.api.image.exception.ImageNotLoadedException -import com.github.rushyverse.api.image.exception.ItemFramesAlreadyExistException -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.future.asDeferred -import net.minestom.server.coordinate.Pos -import net.minestom.server.entity.Entity -import net.minestom.server.entity.EntityType -import net.minestom.server.entity.metadata.other.ItemFrameMeta -import net.minestom.server.instance.Instance -import net.minestom.server.item.ItemStack -import net.minestom.server.item.Material -import net.minestom.server.item.metadata.MapMeta -import net.minestom.server.map.framebuffers.LargeGraphics2DFramebuffer -import net.minestom.server.network.packet.server.SendablePacket -import org.jetbrains.annotations.Blocking -import java.awt.geom.AffineTransform -import java.awt.image.BufferedImage -import java.io.InputStream -import javax.imageio.ImageIO -import kotlin.properties.Delegates - -/** - * Read an image from the resources and build the packets to send to the players. - * @see loadImageAsPacketsFromInputStream - * @receiver Object to display image on the server. - * @param resourceImage Path of the image in the resources. - * @param modifyTransform Function to modify the transform of the image. - * @return The packets list to send to players. - */ -@Blocking -public fun MapImage.loadImageAsPacketsFromResources( - resourceImage: String, - modifyTransform: AffineTransform.(BufferedImage) -> Unit = {} -): Array { - val inputStream = MapImage::class.java.getResourceAsStream("/$resourceImage") - ?: error("Unable to retrieve the image $resourceImage in resources.") - - return inputStream.buffered().use { loadImageAsPacketsFromInputStream(it, modifyTransform) } -} - -/** - * Read an image from an input stream and build the packets to send to the players. - * **This method does not close the provided [inputStream] after the read operation has completed. - * It is the responsibility of the caller to close the stream, if desired.** - * @see [MapImage.loadImageAsPackets] - * @receiver Object to display image on the server. - * @param inputStream Input stream to retrieve the image's data. - * @param modifyTransform Function to modify the transform of the image. - * @return The packets list to send to players. - */ -@Blocking -public fun MapImage.loadImageAsPacketsFromInputStream( - inputStream: InputStream, - modifyTransform: AffineTransform.(BufferedImage) -> Unit = {} -): Array { - val image = ImageIO.read(inputStream) - return loadImageAsPackets(image, modifyTransform) -} - -/** - * A class that allows you to create an Image as Map Item Frame on the server. - * @property packets The packets list to send to new players. - * @property itemFramesPerLine The width blocks size desired for the item frame. The value define the number of item frames by line. - * @property itemFramesPerColumn The height blocks size desired for the item frame. The value define the number of item frames by column. - * @property numberOfItemFrames The number of item frames needed to display the image. - * @property imageLoaded `true` if the image is loaded, `false` otherwise. - * @property itemFrames The list of item frames created. - */ -public class MapImage { - - public companion object { - - /** - * The number of pixels per item frame is 128x128. - */ - public const val MAP_ITEM_FRAME_PIXELS: Int = 128 - - /** - * The number of pixels per item frame is 128. - * So to improve the performance, we will use the bitwise operator to divide by 128. - */ - private const val MAP_ITEM_FRAME_PIXELS_BITWISE = 7 - } - - public var packets: Array? = null - private set - - public var itemFramesPerLine: Int by Delegates.notNull() - private set - - public var itemFramesPerColumn: Int by Delegates.notNull() - private set - - public val imageLoaded: Boolean - get() = packets != null - - private var _itemFrames: List? = null - - public val itemFrames: List? - get() = _itemFrames - - private val numberOfItemFrames: Int - get() = itemFramesPerLine * itemFramesPerColumn - - /** - * Create the packets list to send to new players. - * The result is stored in the [packets] property. - * - * **This method does not close the provided [inputStream] after the read operation has completed. - * It is the responsibility of the caller to close the stream, if desired.** - * - * @param image The image to display. - * @param modifyTransform The function to apply transformation to the image. By default, the image is turned upside down. - * For example, to rotate the image of 90° clockwise, you can use the following code: - * ``` - * // 'this' is the AffineTransform instance. - * // 'it' is the Image instance. - * rotate(Math.toRadians(90.0), it.width / 2.0, it.height / 2.0) - * ``` - * @return The packets list to send to players. - */ - public fun loadImageAsPackets( - image: BufferedImage, - modifyTransform: AffineTransform.(BufferedImage) -> Unit = {} - ): Array { - if (imageLoaded) { - throw ImageAlreadyLoadedException("An image is already loaded using this instance.") - } - - val imageWidth = image.width - val imageHeight = image.height - // We need to round the value to the nearest integer. - // For example : - // If the image is 1x1, we need 1 item frame by line and 1 item frame by column. - // If the image is 129x129, we need 2 item frames by line and 2 item frames by column. - // If the image is 129x128, we need 2 item frames by line and 1 item frame by column. - itemFramesPerLine = (imageWidth + MAP_ITEM_FRAME_PIXELS - 1) ushr MAP_ITEM_FRAME_PIXELS_BITWISE - itemFramesPerColumn = (imageHeight + MAP_ITEM_FRAME_PIXELS - 1) ushr MAP_ITEM_FRAME_PIXELS_BITWISE - - val transform = AffineTransform.getScaleInstance(1.0, 1.0).apply { - modifyTransform(image) - } - - val framebuffer = LargeGraphics2DFramebuffer(imageWidth, imageHeight).apply { - renderer.drawRenderedImage(image, transform) - } - - return createPackets(framebuffer).also { packets = it } - } - - /** - * Creates packets from the image. - * @param framebuffer The frame buffer to convert as packets. - * @return The list of packets. - */ - private fun createPackets(framebuffer: LargeGraphics2DFramebuffer): Array { - val itemFramesPerLine = itemFramesPerLine - return Array(numberOfItemFrames) { - val x = it % itemFramesPerLine - val y = it / itemFramesPerLine - framebuffer.createSubView( - x shl MAP_ITEM_FRAME_PIXELS_BITWISE, - y shl MAP_ITEM_FRAME_PIXELS_BITWISE - ).preparePacket(it) - } - } - - /** - * Create necessary item frames on which the image will be displayed. - * - * **Before calling this method, you must have loaded an image using [loadImageAsPackets].** - * @param instance The instance where you want to create the frame. - * @param pos The position of the frame. - * @param orientation The orientation of the frame. - * @param metaModifier The function to modify the item frame meta. - */ - public suspend fun createItemFrames( - instance: Instance, - pos: Pos, - orientation: ItemFrameMeta.Orientation, - metaModifier: ItemFrameMeta.() -> Unit = { - isInvisible = true - } - ): List { - if (!imageLoaded) { - throw ImageNotLoadedException("An image must be loaded before creating the item frames.") - } - if (atLeastOneItemFrameIsPresent()) { - throw ItemFramesAlreadyExistException("The item frames are already present in the instance.") - } - if (numberOfItemFrames == 0) { - return emptyList().also { _itemFrames = it } - } - - val imageMath = MapImageMath.getFromOrientation(orientation) - val beginX = pos.blockX() - val beginY = pos.blockY() - val beginZ = pos.blockZ() - - // Workaround to avoid unpredictable rotation of the item frames. - val yaw = imageMath.yaw - val pitch = imageMath.pitch - - val entities = List(numberOfItemFrames) { frameNumber -> - Entity(EntityType.ITEM_FRAME).apply { - with(entityMeta as ItemFrameMeta) { - setNotifyAboutChanges(false) - - item = ItemStack.builder(Material.FILLED_MAP) - .meta(MapMeta::class.java) { it.mapId(frameNumber) } - .build() - - this.orientation = orientation - metaModifier() - - setNotifyAboutChanges(true) - } - } - } - - entities.mapIndexed { frameNumber, entity -> - val x = imageMath.computeX(beginX, frameNumber, itemFramesPerLine) - val y = imageMath.computeY(beginY, frameNumber, itemFramesPerLine) - val z = imageMath.computeZ(beginZ, frameNumber, itemFramesPerLine) - entity.setInstance(instance, Pos(x.toDouble(), y.toDouble(), z.toDouble(), yaw, pitch)).asDeferred() - }.awaitAll() - - return entities.also { _itemFrames = it } - } - - /** - * Remove all item frames linked to the image. - * Do nothing if the item frames are not present. - * Will set the [itemFrames] property to `null`. - * @see [Entity.remove] - */ - public fun removeItemFrames() { - val itemFrames = itemFrames ?: return - itemFrames.forEach(Entity::remove) - _itemFrames = null - } - - /** - * Check if all item frames are present. - * If at least one item frame is not present, the function will return `false`. - * @return `true` if at least one item frame is present, `false` otherwise. - */ - private fun atLeastOneItemFrameIsPresent(): Boolean { - return itemFrames?.any { !it.isRemoved } ?: return false - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/image/MapImageMath.kt b/src/main/kotlin/com/github/rushyverse/api/image/MapImageMath.kt deleted file mode 100644 index e8516203..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/image/MapImageMath.kt +++ /dev/null @@ -1,173 +0,0 @@ -package com.github.rushyverse.api.image - -import org.bukkit.block.BlockFace - -/** - * This class is used to calculate the position of the map image. - * The [yaw] and [pitch] values are used to fix the orientation of the item frame. - * Check https://github.com/Minestom/Minestom/issues/760. - * @property yaw Get the pitch for the orientation. - * This is a workaround to fix the orientation of the item frame. - * Without these values, the item frame will be rotated in another direction after several seconds. - * @property pitch Get the pitch for the orientation. - * This is a workaround to fix the orientation of the item frame. - * Without these values, the item frame will be rotated in another direction after several seconds. - */ -public sealed interface MapImageMath { - - public companion object { - - /** - * Link the item frame orientation to the [MapImageMath] instance. - */ - private val orientations = mapOf( - BlockFace.DOWN to Down, - BlockFace.UP to Up, - BlockFace.NORTH to North, - BlockFace.SOUTH to South, - BlockFace.WEST to West, - BlockFace.EAST to East - ) - - /** - * Get the [MapImageMath] linked to the orientation. - * @param orientation The orientation of the item frame. - * @return The [MapImageMath] for the orientation. - */ - public fun getFromOrientation(orientation: BlockFace): MapImageMath { - return orientations[orientation] ?: throw IllegalArgumentException("Unsupported orientation: $orientation") - } - } - - public val yaw: Float - public val pitch: Float - - /** - * Compute the x position of the item frame. - * @param beginX Initial x position. - * @param frameNumber Number of the item frame. - * @param itemFramesPerLine Number of blocks by line. - * @return The x position of the item frame. - */ - public fun computeX(beginX: Int, frameNumber: Int, itemFramesPerLine: Int): Int - - /** - * Compute the y position of the item frame. - * @param beginY Initial y position. - * @param frameNumber Number of the item frame. - * @param itemFramesPerLine Number of blocks by line. - * @return The y position of the item frame. - */ - public fun computeY(beginY: Int, frameNumber: Int, itemFramesPerLine: Int): Int - - /** - * Compute the z position of the item frame. - * @param beginZ Initial z position. - * @param frameNumber Number of the item frame. - * @param itemFramesPerLine Number of blocks by line. - * @return The z position of the item frame. - */ - public fun computeZ(beginZ: Int, frameNumber: Int, itemFramesPerLine: Int): Int - - /** - * Use to calculate the position of the item frame when the orientation is [ItemFrameMeta.Orientation.UP]. - */ - public object Up : MapImageMath { - override val yaw: Float = 0f - override val pitch: Float = 270f - - override fun computeX(beginX: Int, frameNumber: Int, itemFramesPerLine: Int): Int = - beginX + (frameNumber % itemFramesPerLine) - - override fun computeY(beginY: Int, frameNumber: Int, itemFramesPerLine: Int): Int = - beginY - - override fun computeZ(beginZ: Int, frameNumber: Int, itemFramesPerLine: Int): Int = - beginZ + (frameNumber / itemFramesPerLine) - } - - /** - * Use to calculate the position of the item frame when the orientation is [ItemFrameMeta.Orientation.DOWN]. - */ - public object Down : MapImageMath { - override val yaw: Float = 0f - override val pitch: Float = 90f - - override fun computeX(beginX: Int, frameNumber: Int, itemFramesPerLine: Int): Int = - beginX + (frameNumber % itemFramesPerLine) - - override fun computeY(beginY: Int, frameNumber: Int, itemFramesPerLine: Int): Int = - beginY - - override fun computeZ(beginZ: Int, frameNumber: Int, itemFramesPerLine: Int): Int = - beginZ - (frameNumber / itemFramesPerLine) - } - - /** - * Use to calculate the position of the item frame when the orientation is [ItemFrameMeta.Orientation.NORTH]. - */ - public object North : MapImageMath { - override val yaw: Float = 180f - override val pitch: Float = 0f - - override fun computeX(beginX: Int, frameNumber: Int, itemFramesPerLine: Int): Int = - beginX - (frameNumber % itemFramesPerLine) - - override fun computeY(beginY: Int, frameNumber: Int, itemFramesPerLine: Int): Int = - beginY - (frameNumber / itemFramesPerLine) - - override fun computeZ(beginZ: Int, frameNumber: Int, itemFramesPerLine: Int): Int = - beginZ - } - - /** - * Use to calculate the position of the item frame when the orientation is [ItemFrameMeta.Orientation.SOUTH]. - */ - public object South : MapImageMath { - override val yaw: Float = 0f - override val pitch: Float = 0f - - override fun computeX(beginX: Int, frameNumber: Int, itemFramesPerLine: Int): Int = - beginX + (frameNumber % itemFramesPerLine) - - override fun computeY(beginY: Int, frameNumber: Int, itemFramesPerLine: Int): Int = - beginY - (frameNumber / itemFramesPerLine) - - override fun computeZ(beginZ: Int, frameNumber: Int, itemFramesPerLine: Int): Int = - beginZ - } - - /** - * Use to calculate the position of the item frame when the orientation is [ItemFrameMeta.Orientation.EAST]. - */ - public object East : MapImageMath { - override val yaw: Float = 270f - override val pitch: Float = 0f - - override fun computeX(beginX: Int, frameNumber: Int, itemFramesPerLine: Int): Int = - beginX - - override fun computeY(beginY: Int, frameNumber: Int, itemFramesPerLine: Int): Int = - beginY - (frameNumber / itemFramesPerLine) - - override fun computeZ(beginZ: Int, frameNumber: Int, itemFramesPerLine: Int): Int = - beginZ - (frameNumber % itemFramesPerLine) - } - - /** - * Use to calculate the position of the item frame when the orientation is [ItemFrameMeta.Orientation.WEST]. - */ - public object West : MapImageMath { - override val yaw: Float = 90f - override val pitch: Float = 0f - - override fun computeX(beginX: Int, frameNumber: Int, itemFramesPerLine: Int): Int = - beginX - - override fun computeY(beginY: Int, frameNumber: Int, itemFramesPerLine: Int): Int = - beginY - (frameNumber / itemFramesPerLine) - - override fun computeZ(beginZ: Int, frameNumber: Int, itemFramesPerLine: Int): Int = - beginZ + (frameNumber % itemFramesPerLine) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/image/exception/ImageNotLoadedException.kt b/src/main/kotlin/com/github/rushyverse/api/image/exception/ImageNotLoadedException.kt deleted file mode 100644 index b8c5a38d..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/image/exception/ImageNotLoadedException.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.github.rushyverse.api.image.exception - -/** - * Exception when an issue occurs with an image. - */ -public open class ImageException(message: String) : Exception(message) - -/** - * Exception thrown when an image is already loaded. - */ -public open class ImageAlreadyLoadedException(message: String) : ImageException(message) - -/** - * Exception thrown when an image is not loaded. - */ -public open class ImageNotLoadedException(message: String) : ImageException(message) \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/image/exception/ItemFramesAlreadyExistException.kt b/src/main/kotlin/com/github/rushyverse/api/image/exception/ItemFramesAlreadyExistException.kt deleted file mode 100644 index 0d495f69..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/image/exception/ItemFramesAlreadyExistException.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.github.rushyverse.api.image.exception - -/** - * Exception thrown when item frames are already loaded and present in an instance. - */ -public open class ItemFramesAlreadyExistException(message: String) : Exception(message) \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/item/InventoryConditionSuspend.kt b/src/main/kotlin/com/github/rushyverse/api/item/InventoryConditionSuspend.kt deleted file mode 100644 index 036331b2..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/item/InventoryConditionSuspend.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.github.rushyverse.api.item - -import com.github.rushyverse.api.coroutine.MinestomSync -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import net.minestom.server.entity.Player -import net.minestom.server.inventory.click.ClickType -import net.minestom.server.inventory.condition.InventoryCondition -import net.minestom.server.inventory.condition.InventoryConditionResult - -/** - * Converts this [InventoryConditionSuspend] to a [InventoryCondition]. - * When the inventory condition is called, the code will be executed in the current thread. - * However, when the first suspension point is reached, the code will be executed in a thread obtained using [coroutineScope]. - * @receiver Inventory condition suspendable. - * @param coroutineScope Coroutine scope where the inventory condition will be handled. - * @return The native inventory condition. - */ -public fun InventoryConditionSuspend.asNative(coroutineScope: CoroutineScope = Dispatchers.MinestomSync.scope): InventoryCondition { - return InventoryCondition { player, clickedSlot, clickType, result -> - coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) { - this@asNative.accept(player, clickedSlot, clickType, result) - } - } - -} - -/** - * Allows to handle the click event on a specific slot in a coroutine context. - */ -public fun interface InventoryConditionSuspend { - - /** - * Handler of the click event on a specific slot in a coroutine scope. - * According to the implementation, [inventoryConditionResult] can be ignored. - * If the value of [inventoryConditionResult] is changed before the suspension point, the value will be used. - * If the value of [inventoryConditionResult] is changed after the suspension point, the value could be ignored. - * @param player Player who clicked in the inventory. - * @param slot Slot clicked, can be -999 if the click is out of the inventory. - * @param clickType Click type. - * @param inventoryConditionResult Result of this callback. - */ - public suspend fun accept( - player: Player, - slot: Int, - clickType: ClickType, - inventoryConditionResult: InventoryConditionResult - ) - -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/item/ItemComparator.kt b/src/main/kotlin/com/github/rushyverse/api/item/ItemComparator.kt deleted file mode 100644 index ef53863f..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/item/ItemComparator.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.github.rushyverse.api.item - -import org.bukkit.inventory.ItemStack - -/** - * Allows to identify if an item is equivalent to another. - */ -public fun interface ItemComparator { - - public companion object { - /** - * Use the method [ItemStack.isSimilar] to identify if an item is equivalent to another. - */ - public val SIMILAR: ItemComparator = ItemComparator(ItemStack::isSimilar) - - /** - * Use the method [ItemStack.equals] to identify if an item is equivalent to another. - */ - public val EQUALS: ItemComparator = ItemComparator(ItemStack::equals) - } - - /** - * Check if both items are equivalent. - * @param item1 First item. - * @param item2 Second item. - * @return `true` if both items are equivalent, `false` otherwise. - */ - public fun areSame(item1: ItemStack, item2: ItemStack): Boolean -} diff --git a/src/main/kotlin/com/github/rushyverse/api/listener/EventListenerSuspend.kt b/src/main/kotlin/com/github/rushyverse/api/listener/EventListenerSuspend.kt deleted file mode 100644 index a1a6acba..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/listener/EventListenerSuspend.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.github.rushyverse.api.listener - -import com.github.rushyverse.api.coroutine.MinestomSync -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import net.minestom.server.event.Event -import net.minestom.server.event.EventListener - -/** - * Allows to handle event in a coroutine context. - * @param E Type of the event to handle. - * @property coroutineScope Coroutine scope where the event will be handled. - */ -public abstract class EventListenerSuspend( - private val coroutineScope: CoroutineScope = Dispatchers.MinestomSync.scope -) : EventListener { - - override fun run(event: E): EventListener.Result { - coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) { - runSuspend(event) - } - return EventListener.Result.SUCCESS - } - - /** - * Handler of the event in a coroutine scope. - * Before the first suspension point, the code will be executed in the main thread. - * When the first suspension point is reached, the code will be executed in a thread obtained using [coroutineScope]. - * @param event Event that was fired. - */ - protected abstract suspend fun runSuspend(event: E) -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/listener/NPCListenerBuilder.kt b/src/main/kotlin/com/github/rushyverse/api/listener/NPCListenerBuilder.kt deleted file mode 100644 index 5eb55f27..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/listener/NPCListenerBuilder.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.github.rushyverse.api.listener - -import com.github.rushyverse.api.entity.NPCEntity -import net.minestom.server.entity.Player -import net.minestom.server.event.Event -import net.minestom.server.event.EventListener -import net.minestom.server.event.EventNode -import net.minestom.server.event.player.PlayerEntityInteractEvent - -/** - * Builder to create an event node to listen to NPC events. - */ -public object NPCListenerBuilder { - - /** - * Create an event node to listen to NPC events. - * @return A new event node. - */ - public fun createEventNode(): EventNode { - return EventNode.all("npc").apply { - addInteractListener(this) - } - } - - /** - * Add a listener to the node to listen when a player interacts with an NPC. - * @param node Event node to add the listener. - */ - private fun addInteractListener(node: EventNode) { - node.addListener( - EventListener.builder(PlayerEntityInteractEvent::class.java) - .filter { it.target is NPCEntity && it.hand == Player.Hand.MAIN } - .handler { - val npc = it.target as NPCEntity - npc.onInteract(it) - } - .build() - ) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/position/AbstractArea.kt b/src/main/kotlin/com/github/rushyverse/api/position/AbstractArea.kt deleted file mode 100644 index 73fad1c4..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/position/AbstractArea.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.github.rushyverse.api.position - -import org.bukkit.entity.Entity -import java.util.* - -/** - * An area that contains entities. - * @param E The type of entity. - * @property _entitiesInArea The mutable entities in the area. - * @property entitiesInArea The entities in the area. - */ -public abstract class AbstractArea : IArea { - - private val _entitiesInArea: MutableSet = Collections.synchronizedSet(mutableSetOf()) - - override val entitiesInArea: Set - get() = _entitiesInArea - - /** - * Update the entities in the area. - * @param inArea The entities in the area. - * @return A pair of the entities that were added and the entities that were removed. - */ - protected fun update(inArea: Set): Pair, Collection> { - val enter = inArea - entitiesInArea - val quit = entitiesInArea - inArea - - _entitiesInArea.clear() - _entitiesInArea.addAll(inArea) - return enter to quit - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/position/CubeArea.kt b/src/main/kotlin/com/github/rushyverse/api/position/CubeArea.kt deleted file mode 100644 index f3384ef7..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/position/CubeArea.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.github.rushyverse.api.position - -import com.github.rushyverse.api.extension.centerRelative -import com.github.rushyverse.api.extension.isInCube -import com.github.rushyverse.api.extension.minMaxOf -import org.bukkit.Location -import org.bukkit.World -import org.bukkit.entity.Entity - -/** - * A cuboid area defined by two positions. - * @param E Type of entity. - * @property entityClass Class of the entity. - * @property min Minimum position. - * @property max Maximum position. - * @property position Center position of the cube - */ -public class CubeArea( - public val entityClass: Class, - public override var world: World, - location1: Location, - location2: Location -) : AbstractArea(), IAreaLocatable { - - public companion object { - public inline operator fun invoke( - world: World, - location1: Location, - location2: Location - ): CubeArea = CubeArea(E::class.java, world, location1, location2) - } - - override var location: Location - get() = max.centerRelative(min) - set(value) { - // The new position becomes the center of the cube. - val halfSize = max.toVector().subtract(min.toVector()).multiply(0.5) - min = value.toVector().subtract(halfSize).toLocation(value.world) - max = value.toVector().add(halfSize).toLocation(value.world) - } - - public var min: Location - private set - - public var max: Location - private set - - init { - val (x1, x2) = minMaxOf(location1.x(), location2.x()) - val (y1, y2) = minMaxOf(location1.y(), location2.y()) - val (z1, z2) = minMaxOf(location1.z(), location2.z()) - this.min = Location(world, x1, y1, z1) - this.max = Location(world, x2, y2, z2) - } - - override fun updateEntitiesInArea(): Pair, Collection> { - return update(world.entities - .asSequence() - .filterIsInstance(entityClass) - .filter { it.location.isInCube(min, max) } - .toSet() - ) - } -} - - diff --git a/src/main/kotlin/com/github/rushyverse/api/position/CylinderArea.kt b/src/main/kotlin/com/github/rushyverse/api/position/CylinderArea.kt deleted file mode 100644 index ea52db6f..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/position/CylinderArea.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.github.rushyverse.api.position - -import com.github.rushyverse.api.extension.isInCylinder -import org.bukkit.Location -import org.bukkit.World -import org.bukkit.entity.Entity - -/** - * An area defined by a cylinder shape. - * @param E Type of entity. - * @property entityClass Class of the entity. - * @property limitY Limit of the y-axis. - * @property radius Radius. - */ -public class CylinderArea( - public val entityClass: Class, - public override var world: World, - public override var location: Location, - radius: Double, - public var limitY: ClosedRange, -) : AbstractArea(), IAreaLocatable { - - public companion object { - public inline operator fun invoke( - world: World, - location: Location, - radius: Double, - limitY: ClosedRange - ): CylinderArea = CylinderArea(E::class.java, world, location, radius, limitY) - } - - public var radius: Double = radius - set(value) { - verifyNewRadiusValue(value) - field = value - } - - init { - verifyNewRadiusValue(radius) - } - - /** - * Verifies that the new radius value is greater than or equal to 0.0. - * @param value New radius value. - */ - private fun verifyNewRadiusValue(value: Double) { - require(value >= 0.0) { "Radius must be greater than or equal to 0.0" } - } - - override fun updateEntitiesInArea(): Pair, Collection> { - val cylinderPosition = location - return update( - world.entities - .asSequence() - .filterIsInstance(entityClass) - .filter { it.location.isInCylinder(cylinderPosition, radius, limitY) } - .toSet() - ) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/position/IArea.kt b/src/main/kotlin/com/github/rushyverse/api/position/IArea.kt deleted file mode 100644 index 51317718..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/position/IArea.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.github.rushyverse.api.position - -import org.bukkit.entity.Entity - -/** - * An area that contains entities. - * @param E The type of entity. - * @property entitiesInArea The entities in the area. - */ -public interface IArea { - - public val entitiesInArea: Set - - /** - * Compute and update the entities in the area. - * @return A pair of the entities that were added and the entities that were removed. - */ - public fun updateEntitiesInArea(): Pair, Collection> -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/position/IAreaLocatable.kt b/src/main/kotlin/com/github/rushyverse/api/position/IAreaLocatable.kt deleted file mode 100644 index 138655b8..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/position/IAreaLocatable.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.github.rushyverse.api.position - -import org.bukkit.Location -import org.bukkit.World -import org.bukkit.entity.Entity - -/** - * An area with a location into an instance. - * @param E Type of entity. - * @property world World where is located the area. - * @property location Location of the area. - */ -public interface IAreaLocatable : IArea { - - public var world: World - - public var location: Location -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/position/MultiArea.kt b/src/main/kotlin/com/github/rushyverse/api/position/MultiArea.kt deleted file mode 100644 index 9fe95548..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/position/MultiArea.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.github.rushyverse.api.position - -import org.bukkit.entity.Entity - -/** - * An area corresponding to multiple areas. - * @param E Type of entity. - * @property _areas Mutable collection of areas. - * @property areas Collection of areas. - */ -public class MultiArea(areas: MutableSet> = mutableSetOf()) : AbstractArea() { - - private val _areas: MutableSet> = areas - public val areas: Set> get() = _areas - - /** - * Adds an area. - * @param area Area to add. - * @return `true` if the area was added, `false` otherwise. - */ - public fun addArea(area: IArea): Boolean = _areas.add(area) - - /** - * Removes an area. - * @param area Area to remove. - * @return `true` if the area was removed, `false` otherwise. - */ - public fun removeArea(area: IArea): Boolean = _areas.remove(area) - - /** - * Removes all areas. - */ - public fun removeAllAreas() { - _areas.clear() - } - - override fun updateEntitiesInArea(): Pair, Collection> { - return update( - areas.asSequence() - .onEach { it.updateEntitiesInArea() } - .flatMap { it.entitiesInArea } - .toSet() - ) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/position/SphereArea.kt b/src/main/kotlin/com/github/rushyverse/api/position/SphereArea.kt deleted file mode 100644 index dfc9777d..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/position/SphereArea.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.github.rushyverse.api.position - -import org.bukkit.Location -import org.bukkit.World -import org.bukkit.entity.Entity - -/** - * An area defined by a sphere shape. - * @param E Type of entity. - * @property entityClass Class of the entity. - * @property radius Radius. - */ -public class SphereArea( - public val entityClass: Class, - public override var world: World, - public override var location: Location, - radius: Double -) : AbstractArea(), IAreaLocatable { - - public companion object { - public inline operator fun invoke( - world: World, - location: Location, - radius: Double - ): SphereArea = SphereArea(E::class.java, world, location, radius) - } - - public var radius: Double = radius - set(value) { - verifyNewRadiusValue(value) - field = value - } - - init { - verifyNewRadiusValue(radius) - } - - /** - * Verifies that the new radius value is greater than or equal to 0.0. - * @param value New radius value. - */ - private fun verifyNewRadiusValue(value: Double) { - require(value >= 0.0) { "Radius must be greater than or equal to 0.0" } - } - - override fun updateEntitiesInArea(): Pair, Collection> { - return update(world.getNearbyEntities(location, radius, radius, radius).asSequence().filterIsInstance(entityClass).toSet()) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/ItemFrameMetaOrientationSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/ItemFrameMetaOrientationSerializer.kt deleted file mode 100644 index 07aaf5af..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/serializer/ItemFrameMetaOrientationSerializer.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.github.rushyverse.api.serializer - -import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerializationException -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import org.bukkit.block.BlockFace - -/** - * Serializer for ItemFrame [BlockFace] orientation. - * To deserialize the orientation, it will be case-insensitive. - */ -public object ItemFrameMetaOrientationSerializer : KSerializer { - - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("orientation", PrimitiveKind.STRING) - - override fun serialize(encoder: Encoder, value: BlockFace) { - encoder.encodeString(value.name) - } - - override fun deserialize(decoder: Decoder): BlockFace { - val decodeString = decoder.decodeString() - return BlockFace.values() - .find { it.name.equals(decodeString, true) } - ?: throw SerializationException("Invalid orientation") - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/PosSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/PosSerializer.kt deleted file mode 100644 index a91ffb21..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/serializer/PosSerializer.kt +++ /dev/null @@ -1,112 +0,0 @@ -package com.github.rushyverse.api.serializer - -import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerializationException -import kotlinx.serialization.builtins.nullable -import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.descriptors.buildClassSerialDescriptor -import kotlinx.serialization.encoding.* -import org.bukkit.Location -import org.bukkit.World - -/** - * Serializer for [Pos]. - */ -public object PosSerializer : KSerializer { - - /** - * Serializer for the coordinates x, y or z. - */ - private val coordinateSerializer get() = Double.serializer() - - /** - * Serializer for the rotations yaw or pitch. - */ - private val rotationSerializer get() = Float.serializer().nullable - - override val descriptor: SerialDescriptor = buildClassSerialDescriptor("pos") { - val coordinateSerializer = coordinateSerializer - val rotationSerializer = rotationSerializer - - element("x", coordinateSerializer.descriptor) - element("y", coordinateSerializer.descriptor) - element("z", coordinateSerializer.descriptor) - element("yaw", rotationSerializer.descriptor) - element("pitch", rotationSerializer.descriptor) - } - - override fun serialize(encoder: Encoder, value: Pos) { - val coordinateSerializer = coordinateSerializer - val rotationSerializer = rotationSerializer - - encoder.encodeStructure(descriptor) { - encodeSerializableElement(descriptor, 0, coordinateSerializer, value.x) - encodeSerializableElement(descriptor, 1, coordinateSerializer, value.y) - encodeSerializableElement(descriptor, 2, coordinateSerializer, value.z) - encodeSerializableElement(descriptor, 3, rotationSerializer, value.yaw) - encodeSerializableElement(descriptor, 4, rotationSerializer, value.pitch) - } - } - - override fun deserialize(decoder: Decoder): Pos { - val coordinateSerializer = coordinateSerializer - val rotationSerializer = rotationSerializer - - return decoder.decodeStructure(descriptor) { - var x: Double? = null - var y: Double? = null - var z: Double? = null - var yaw: Float? = null - var pitch: Float? = null - - if (decodeSequentially()) { - x = decodeSerializableElement(descriptor, 0, coordinateSerializer) - y = decodeSerializableElement(descriptor, 1, coordinateSerializer) - z = decodeSerializableElement(descriptor, 2, coordinateSerializer) - yaw = decodeSerializableElement(descriptor, 3, rotationSerializer) - pitch = decodeSerializableElement(descriptor, 4, rotationSerializer) - } else { - while (true) { - when (val index = decodeElementIndex(descriptor)) { - 0 -> x = decodeSerializableElement(descriptor, index, coordinateSerializer) - 1 -> y = decodeSerializableElement(descriptor, index, coordinateSerializer) - 2 -> z = decodeSerializableElement(descriptor, index, coordinateSerializer) - 3 -> yaw = decodeSerializableElement(descriptor, index, rotationSerializer) - 4 -> pitch = decodeSerializableElement(descriptor, index, rotationSerializer) - CompositeDecoder.DECODE_DONE -> break - else -> error("Unexpected index: $index") - } - } - } - - Pos( - x ?: throw SerializationException("The field x is missing"), - y ?: throw SerializationException("The field y is missing"), - z ?: throw SerializationException("The field z is missing"), - yaw ?: 0f, - pitch ?: 0f - ) - } - } -} - -/** - * Represents a position in a 3D space with additional yaw and pitch rotations. - * Can be converted to a Bukkit [Location] with the provided [toLocation] method. - * - * @property x The x-coordinate of the position. - * @property y The y-coordinate of the position. - * @property z The z-coordinate of the position. - * @property yaw The yaw rotation (horizontal rotation) in degrees. - * @property pitch The pitch rotation (vertical rotation) in degrees. - */ -public data class Pos(val x: Double, val y: Double, val z: Double, val yaw: Float, val pitch: Float) { - /** - * Converts this [Pos] object to a Bukkit [Location] object. - * - * @param world The [World] in which the position is located. - * @return A new [Location] object representing the same position and rotation as this [Pos] object. - */ - public fun toLocation(world: World): Location = Location(world, x, y, z, yaw, pitch) -} diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleNotRegisteredException.kt b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleNotRegisteredException.kt deleted file mode 100644 index 5fc5442a..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleNotRegisteredException.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.github.rushyverse.api.translation - -import java.util.* - -/** - * Exception used when the system try to use a resource bundle not registered. - * @param bundleName Name of the bundle. - * @param locale Locale searched. - */ -public class ResourceBundleNotRegisteredException(public val bundleName: String, public val locale: Locale) : - RuntimeException("The bundle [$bundleName] for locale [$locale] is not registered.") \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationsProvider.kt b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationsProvider.kt deleted file mode 100644 index 2d3eb276..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationsProvider.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.github.rushyverse.api.translation - -import com.ibm.icu.text.MessageFormat -import mu.KotlinLogging -import java.util.* - -/** - * Loads the [ResourceBundle] called [bundleName] for all supported locales from [SupportedLanguage]. - * @see ResourceBundleTranslationsProvider.registerResourceBundle - */ -public fun ResourceBundleTranslationsProvider.registerResourceBundleForSupportedLocales( - bundleName: String, - loader: (String, Locale) -> ResourceBundle -) { - SupportedLanguage.values().forEach { - registerResourceBundle(bundleName, it.locale, loader) - } -} - -private val logger = KotlinLogging.logger { } - -/** - * Translation provider backed by Java's [ResourceBundle]s. This makes use of `.properties` files that are standard - * across the Java ecosystem. - */ -public open class ResourceBundleTranslationsProvider : TranslationsProvider() { - - private val bundles: MutableMap, ResourceBundle> = mutableMapOf() - - override fun get(key: String, locale: Locale, bundleName: String): String { - return getBundle(locale, bundleName).getString(key) - } - - override fun translate( - key: String, - locale: Locale, - bundleName: String, - replacements: Array - ): String { - val string = try { - get(key, locale, bundleName) - } catch (e: MissingResourceException) { - logger.error("Unable to find translation for key '$key' in bundles: '$bundleName'") - return key - } - - return MessageFormat(string, locale).format(replacements) - } - - /** - * Retrieve the registered bundle or load it. - * @param locale Locale. - * @param bundleName Name of the bundle. - * @return The loaded instance of [ResourceBundle]. - */ - protected open fun getBundle(locale: Locale, bundleName: String): ResourceBundle { - return bundles[createKey(bundleName, locale)] - ?: throw ResourceBundleNotRegisteredException(bundleName, locale) - } - - /** - * Loads the [ResourceBundle] called [bundleName] for [locale] and register it in [bundles] - * @param bundleName Name of the bundle. - * @param locale Locale. - * @return The loaded instance of [ResourceBundle]. - */ - public fun registerResourceBundle( - bundleName: String, - locale: Locale, - loader: (String, Locale) -> ResourceBundle - ): ResourceBundle { - logger.info("Getting bundle $bundleName for locale $locale") - return loader(bundleName, locale).apply { - bundles[createKey(bundleName, locale)] = this - } - } - - /** - * Create the key to retrieve bundle. - * @param bundleName Name of the bundle. - * @param locale Locale. - * @return The key created. - */ - private fun createKey(bundleName: String, locale: Locale) = bundleName to locale -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt b/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt deleted file mode 100644 index d8a32446..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.rushyverse.api.translation - -import java.util.* - - -/** - * List of supported locales to translate keys. - */ -public enum class SupportedLanguage(public val displayName: String, public val locale: Locale) { - - ENGLISH("English", Locale("en", "gb")), - FRENCH("Français", Locale("fr", "fr")) - -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/TranslationsProvider.kt b/src/main/kotlin/com/github/rushyverse/api/translation/TranslationsProvider.kt deleted file mode 100644 index 82c81bb9..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/translation/TranslationsProvider.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.github.rushyverse.api.translation - -import java.util.* - -/** - * Translation provider interface, in charge of taking string keys and returning translated strings. - */ -public abstract class TranslationsProvider { - - /** - * Get a translation by key from the given locale and bundle name. - */ - public abstract fun get(key: String, locale: Locale, bundleName: String): String - - /** - * Get a formatted translation using the provided arguments. - */ - public abstract fun translate( - key: String, - locale: Locale, - bundleName: String, - replacements: Array - ): String - - /** - * Get a formatted translation using the provided arguments. - */ - public fun translate( - key: String, - locale: Locale, - bundleName: String - ): String = translate(key, locale, bundleName, emptyArray()) - - /** - * Get a formatted translation using the provided arguments. - */ - public fun translate( - key: String, - locale: Locale, - bundleName: String, - replacements: Collection - ): String = translate(key, locale, bundleName, replacements.toTypedArray()) -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/utils/FileUtils.kt b/src/main/kotlin/com/github/rushyverse/api/utils/FileUtils.kt deleted file mode 100644 index 06ecaffd..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/utils/FileUtils.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.github.rushyverse.api.utils - -import java.io.File - -/** - * Get the current directory where is executed the program. - */ -public val workingDirectory: File - get() = File(System.getProperty("user.dir")) \ No newline at end of file From 6a7d9e24957553cbbc07303292917dbee703361b Mon Sep 17 00:00:00 2001 From: Quentin Date: Thu, 20 Jul 2023 19:16:45 +0200 Subject: [PATCH 017/143] chore: Delete old tests Deleting old tests before import working code for paper --- .../com/github/rushyverse/api/AbstractTest.kt | 88 -- .../github/rushyverse/api/RushyServerTest.kt | 231 ---- .../api/command/CommandMessagesTest.kt | 55 - .../BungeeCordConfigurationTest.kt | 55 - .../HoconConfigurationReaderTest.kt | 90 -- .../configuration/IConfigurationReaderTest.kt | 99 -- .../VelocityConfigurationTest.kt | 55 - .../MinestomAsyncCoroutineDispatcherTest.kt | 44 - .../MinestomCoroutineDispatcherTest.kt | 53 - .../MinestomSyncCoroutineDispatcherTest.kt | 44 - .../api/entity/CommonNPCEntityTest.kt | 307 ----- .../rushyverse/api/entity/NPCEntityTest.kt | 26 - .../api/entity/PlayerNPCEntityTest.kt | 185 --- .../api/extension/AcquirableExtTest.kt | 96 -- .../api/extension/CommandExtTest.kt | 129 -- .../api/extension/ComponentExtTest.kt | 297 ---- .../rushyverse/api/extension/EntityExtTest.kt | 237 ---- .../api/extension/InventoryExtTest.kt | 1210 ----------------- .../api/extension/ItemStackExtTest.kt | 361 ----- .../api/extension/ListenerExtTest.kt | 51 - .../rushyverse/api/extension/MathExtTest.kt | 53 - .../rushyverse/api/extension/NumberExtTest.kt | 97 -- .../rushyverse/api/extension/PosExtTest.kt | 143 -- .../api/extension/PropertyExtTest.kt | 28 - .../rushyverse/api/extension/StringExtTest.kt | 341 ----- .../rushyverse/api/image/MapImageMathTest.kt | 806 ----------- .../rushyverse/api/image/MapImageTest.kt | 722 ---------- .../api/item/InventoryConditionSuspendTest.kt | 71 - .../rushyverse/api/item/ItemComparatorTest.kt | 61 - .../api/listener/EventListenerSuspendTest.kt | 67 - .../api/listener/NPCListenerBuilderTest.kt | 77 -- .../api/position/AbstractAreaTest.kt | 104 -- .../rushyverse/api/position/CubeAreaTest.kt | 260 ---- .../api/position/CylinderAreaTest.kt | 353 ----- .../rushyverse/api/position/MultiAreaTest.kt | 247 ---- .../rushyverse/api/position/SphereAreaTest.kt | 183 --- .../api/serializer/PosSerializerTest.kt | 212 --- .../ResourceBundleTranslationsProviderTest.kt | 204 --- .../github/rushyverse/api/utils/Asserts.kt | 10 - .../rushyverse/api/utils/FileUtilsTest.kt | 21 - .../github/rushyverse/api/utils/Generator.kt | 25 - 41 files changed, 7798 deletions(-) delete mode 100644 src/test/kotlin/com/github/rushyverse/api/AbstractTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/RushyServerTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/command/CommandMessagesTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/configuration/BungeeCordConfigurationTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReaderTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/configuration/IConfigurationReaderTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/configuration/VelocityConfigurationTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/coroutine/MinestomAsyncCoroutineDispatcherTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/coroutine/MinestomCoroutineDispatcherTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/coroutine/MinestomSyncCoroutineDispatcherTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/entity/CommonNPCEntityTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/entity/NPCEntityTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/entity/PlayerNPCEntityTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/extension/AcquirableExtTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/extension/CommandExtTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/extension/ComponentExtTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/extension/EntityExtTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/extension/InventoryExtTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/extension/ItemStackExtTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/extension/ListenerExtTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/extension/MathExtTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/extension/NumberExtTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/extension/PosExtTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/extension/PropertyExtTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/image/MapImageMathTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/image/MapImageTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/item/InventoryConditionSuspendTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/item/ItemComparatorTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/listener/EventListenerSuspendTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/listener/NPCListenerBuilderTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/position/AbstractAreaTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/position/CubeAreaTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/position/CylinderAreaTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/position/MultiAreaTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/position/SphereAreaTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/serializer/PosSerializerTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationsProviderTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/utils/Asserts.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/utils/FileUtilsTest.kt delete mode 100644 src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt diff --git a/src/test/kotlin/com/github/rushyverse/api/AbstractTest.kt b/src/test/kotlin/com/github/rushyverse/api/AbstractTest.kt deleted file mode 100644 index 79e7b54f..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/AbstractTest.kt +++ /dev/null @@ -1,88 +0,0 @@ -package com.github.rushyverse.api - -import com.github.rushyverse.api.configuration.* -import com.github.rushyverse.api.utils.getAvailablePort -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.hocon.Hocon -import org.junit.jupiter.api.io.TempDir -import java.io.File -import kotlin.test.AfterTest -import kotlin.test.BeforeTest - -@Serializable -data class TestConfiguration( - override val server: ServerConfiguration -) : IConfiguration - -@SerialName("server") -@Serializable -data class ServerConfiguration( - override val port: Int, - override val world: String, - override val onlineMode: Boolean, - override val velocity: VelocityConfiguration, - override val bungeeCord: BungeeCordConfiguration -) : IServerConfiguration - -abstract class AbstractTest { - - companion object { - private const val PROPERTY_USER_DIR = "user.dir" - const val DEFAULT_WORLD = "world" - } - - @TempDir - lateinit var tmpDirectory: File - - private lateinit var initCurrentDirectory: String - - protected val expectedDefaultConfiguration: TestConfiguration - get() = TestConfiguration( - ServerConfiguration( - 25565, - DEFAULT_WORLD, - false, - VelocityConfiguration(false, ""), - BungeeCordConfiguration(false, emptySet()) - ) - ) - - @BeforeTest - open fun onBefore() { - initCurrentDirectory = System.getProperty(PROPERTY_USER_DIR) - System.setProperty(PROPERTY_USER_DIR, tmpDirectory.absolutePath) - } - - @AfterTest - open fun onAfter() { - System.setProperty(PROPERTY_USER_DIR, initCurrentDirectory) - } - - protected fun fileOfTmpDirectory(fileName: String) = File(tmpDirectory, fileName) - - protected fun configurationToHocon(configuration: TestConfiguration) = - Hocon.encodeToConfig(TestConfiguration.serializer(), configuration) - - protected fun configurationToHoconFile( - configuration: TestConfiguration, - file: File = fileOfTmpDirectory(IConfigurationReader.DEFAULT_CONFIG_FILE_NAME) - ) = - file.writeText(configurationToHocon(configuration).root().render()) - - protected fun copyFolderFromResourcesToFolder(folderName: String, destination: File) { - val folder = File(javaClass.classLoader.getResource(folderName)!!.file) - folder.copyRecursively(destination) - } - - protected fun copyWorldInTmpDirectory( - configuration: TestConfiguration = defaultConfigurationOnAvailablePort() - ) { - val worldFile = fileOfTmpDirectory(configuration.server.world) - copyFolderFromResourcesToFolder(DEFAULT_WORLD, worldFile) - } - - protected fun defaultConfigurationOnAvailablePort() = expectedDefaultConfiguration.let { - it.copy(server = it.server.copy(port = getAvailablePort())) - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/RushyServerTest.kt b/src/test/kotlin/com/github/rushyverse/api/RushyServerTest.kt deleted file mode 100644 index c9a08cbd..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/RushyServerTest.kt +++ /dev/null @@ -1,231 +0,0 @@ -package com.github.rushyverse.api - -import com.github.rushyverse.api.command.GamemodeCommand -import com.github.rushyverse.api.command.GiveCommand -import com.github.rushyverse.api.command.KickCommand -import com.github.rushyverse.api.command.StopCommand -import com.github.rushyverse.api.configuration.* -import com.github.rushyverse.api.utils.randomString -import kotlinx.coroutines.test.runTest -import net.minestom.server.MinecraftServer -import net.minestom.server.extras.MojangAuth -import net.minestom.server.extras.bungee.BungeeCordProxy -import net.minestom.server.extras.velocity.VelocityProxy -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import java.io.IOException -import kotlin.test.* - -class TestServer(private val configuration: String? = null) : RushyServer() { - - override suspend fun start() { - start(configuration) { - registerCommands() - } - } -} - -class RushyServerTest : AbstractTest() { - - @AfterTest - override fun onAfter() { - super.onAfter() - if (MinecraftServer.process() != null) { - MinecraftServer.stopCleanly() - } - } - - @Test - fun `should have the correct bundle name`() { - assertEquals("api", RushyServer.API.BUNDLE_API) - } - - @Nested - inner class CreateOrGetConfiguration { - - @Test - fun `should create a configuration file if it doesn't exist`() = runTest { - assertThrows { - TestServer().start() - } - val configurationFile = fileOfTmpDirectory(IConfigurationReader.DEFAULT_CONFIG_FILE_NAME) - assertTrue { configurationFile.isFile } - - val configuration = HoconConfigurationReader().readConfigurationFile(configurationFile) - assertEquals(expectedDefaultConfiguration, configuration) - } - - @Test - fun `should use the configuration file if exists`() = runTest { - val configurationFile = fileOfTmpDirectory(randomString()) - assertTrue { configurationFile.createNewFile() } - - val configuration = defaultConfigurationOnAvailablePort() - configurationToHoconFile(configuration, configurationFile) - - val exception = assertThrows { - TestServer(configurationFile.absolutePath).start() - } - assertEquals(configuration.server.world, exception.file.name) - } - - } - - @Nested - inner class UseConfiguration { - - @Test - fun `should use configuration to turn on the server`() = runTest { - val configuration = defaultConfigurationOnAvailablePort() - val configurationFile = fileOfTmpDirectory(randomString()) - configurationToHoconFile(configuration, configurationFile) - - copyWorldInTmpDirectory(configuration) - - TestServer(configurationFile.absolutePath).start() - - // If no exception is thrown, the world is loaded - assertTrue { MinecraftServer.isStarted() } - - val server = MinecraftServer.getServer() - assertEquals(configuration.server.port, server.port) - assertEquals("0.0.0.0", server.address) - } - } - - @Nested - inner class Command { - - @Test - fun `should load all commands`() = runTest { - copyWorldInTmpDirectory() - TestServer().start() - - val commandManager = MinecraftServer.getCommandManager() - assertContentEquals( - commandManager.commands.asSequence().map { it::class.java }.sortedBy { it.simpleName }.toList(), - sequenceOf( - StopCommand::class.java, - KickCommand::class.java, - GiveCommand::class.java, - GamemodeCommand::class.java - ).sortedBy { it.simpleName }.toList() - ) - } - } - - @Nested - inner class Velocity { - - @BeforeTest - fun onBefore() { - VelocityProxy::class.java.getDeclaredField("enabled").apply { - isAccessible = true - setBoolean(null, false) - } - } - - @Test - fun `should load velocity`() = runTest { - test(true, "secret") - } - - @Test - fun `should not load velocity`() = runTest { - test(false, "") - } - - private suspend fun test(enabled: Boolean, secret: String) { - val defaultConfiguration = expectedDefaultConfiguration - val configuration = expectedDefaultConfiguration.copy( - defaultConfiguration.server.copy( - velocity = VelocityConfiguration(enabled, secret) - ) - ) - configurationToHoconFile(configuration) - copyWorldInTmpDirectory(configuration) - TestServer().start() - - assertEquals(enabled, VelocityProxy.isEnabled()) - } - } - - @Nested - inner class BungeeCord { - - @BeforeTest - fun onBefore() { - BungeeCordProxy::class.java.getDeclaredField("enabled").apply { - isAccessible = true - setBoolean(null, false) - } - BungeeCordProxy.setBungeeGuardTokens(null) - } - - @Test - fun `should load bungeecord`() = runTest { - test(true, "test") - assertTrue(BungeeCordProxy.isValidBungeeGuardToken("test")) - } - - @Test - fun `should not load bungeecord`() = runTest { - test(false, "") - } - - private suspend fun test(enabled: Boolean, secret: String) { - val defaultConfiguration = expectedDefaultConfiguration - val configuration = expectedDefaultConfiguration.copy( - server = defaultConfiguration.server.copy( - bungeeCord = BungeeCordConfiguration(enabled, setOf(secret)) - ) - ) - - configurationToHoconFile(configuration) - copyWorldInTmpDirectory(configuration) - - TestServer().start() - - assertEquals(enabled, BungeeCordProxy.isEnabled()) - assertEquals(enabled, BungeeCordProxy.isBungeeGuardEnabled()) - } - } - - @Nested - inner class OnlineMode { - - @BeforeTest - fun onBefore() { - MojangAuth::class.java.getDeclaredField("enabled").apply { - isAccessible = true - setBoolean(null, false) - } - } - - @Test - fun `should set online mode`() = runTest { - test(true) - } - - @Test - fun `should set offline mode`() = runTest { - test(false) - } - - private suspend fun test(onlineMode: Boolean) { - val defaultConfiguration = expectedDefaultConfiguration - val configuration = expectedDefaultConfiguration.copy( - server = defaultConfiguration.server.copy( - onlineMode = onlineMode - ) - ) - configurationToHoconFile(configuration) - copyWorldInTmpDirectory(configuration) - TestServer().start() - - assertEquals(onlineMode, MojangAuth.isEnabled()) - } - - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/command/CommandMessagesTest.kt b/src/test/kotlin/com/github/rushyverse/api/command/CommandMessagesTest.kt deleted file mode 100644 index a161e789..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/command/CommandMessagesTest.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.github.rushyverse.api.command - -import io.mockk.every -import io.mockk.mockk -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.format.NamedTextColor -import net.minestom.server.command.CommandSender -import net.minestom.server.entity.Player -import org.junit.jupiter.api.Nested -import kotlin.test.Test -import kotlin.test.assertEquals - -class CommandMessagesTest { - - @Nested - inner class SendPlayerNotFoundMessage { - - @Test - fun `should send component translatable if sender is player`() { - val sender = mockk() - var component: Component? = null - every { sender.sendMessage(any()) } answers { - component = arg(0) - } - CommandMessages.sendPlayerNotFoundMessage(sender) - - assertEquals(Component.translatable("argument.entity.notfound.player", NamedTextColor.RED), component) - } - - @Test - fun `should send component text if sender is not player`() { - val sender = mockk() - var component: Component? = null - every { sender.sendMessage(any()) } answers { - component = arg(0) - } - CommandMessages.sendPlayerNotFoundMessage(sender) - - assertEquals(Component.text("No player was found", NamedTextColor.RED), component) - } - - } - - @Test - fun `should send missing permission message`() { - val sender = mockk() - var component: Component? = null - every { sender.sendMessage(any()) } answers { - component = arg(0) - } - CommandMessages.sendMissingPermissionMessage(sender) - - assertEquals(Component.translatable("commands.help.failed", NamedTextColor.RED), component) - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/configuration/BungeeCordConfigurationTest.kt b/src/test/kotlin/com/github/rushyverse/api/configuration/BungeeCordConfigurationTest.kt deleted file mode 100644 index c606b91a..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/configuration/BungeeCordConfigurationTest.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.github.rushyverse.api.configuration - -import com.github.rushyverse.api.utils.randomString -import kotlinx.serialization.json.Json -import org.junit.jupiter.api.Nested -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource -import kotlin.test.assertEquals - -class BungeeCordConfigurationTest { - - @Nested - inner class Serialize { - - @ValueSource(booleans = [true, false]) - @ParameterizedTest - fun `with empty secrets`(enabled: Boolean) { - val configuration = BungeeCordConfiguration(enabled, emptySet()) - val serialize = Json.encodeToString(BungeeCordConfiguration.serializer(), configuration) - assertEquals("{\"enabled\":$enabled,\"secrets\":[]}", serialize) - } - - @ValueSource(booleans = [true, false]) - @ParameterizedTest - fun `with non empty secrets`(enabled: Boolean) { - val secrets = listOf(randomString(), randomString()) - val configuration = BungeeCordConfiguration(enabled, secrets.toSet()) - val serialize = Json.encodeToString(BungeeCordConfiguration.serializer(), configuration) - assertEquals("{\"enabled\":$enabled,\"secrets\":[\"${secrets[0]}\",\"${secrets[1]}\"]}", serialize) - } - - } - - @Nested - inner class Deserialize { - - @ValueSource(booleans = [true, false]) - @ParameterizedTest - fun `with empty secrets`(enabled: Boolean) { - val string = "{\"enabled\":$enabled,\"secrets\":[]}" - val deserialize = Json.decodeFromString(BungeeCordConfiguration.serializer(), string) - assertEquals(BungeeCordConfiguration(enabled, emptySet()), deserialize) - } - - @ValueSource(booleans = [true, false]) - @ParameterizedTest - fun `with non empty secrets`(enabled: Boolean) { - val secrets = listOf(randomString(), randomString()) - val string = "{\"enabled\":$enabled,\"secrets\":[\"${secrets[0]}\",\"${secrets[1]}\"]}" - val deserialize = Json.decodeFromString(BungeeCordConfiguration.serializer(), string) - assertEquals(BungeeCordConfiguration(enabled, secrets.toSet()), deserialize) - } - - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReaderTest.kt b/src/test/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReaderTest.kt deleted file mode 100644 index 852539ce..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReaderTest.kt +++ /dev/null @@ -1,90 +0,0 @@ -package com.github.rushyverse.api.configuration - -import com.github.rushyverse.api.AbstractTest -import com.github.rushyverse.api.TestConfiguration -import com.typesafe.config.ConfigFactory -import kotlinx.serialization.hocon.decodeFromConfig -import net.minestom.server.coordinate.Pos -import org.junit.jupiter.api.Nested -import java.io.File -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals - -class HoconConfigurationReaderTest : AbstractTest() { - - private lateinit var file: File - - @BeforeTest - override fun onBefore() { - super.onBefore() - file = fileOfTmpDirectory("test.conf") - } - - @Nested - inner class HoconDefaultConfiguration { - - @Test - fun `should support Pos serializer`() { - val hocon = HoconConfigurationReader.hoconDefault - - val expected = Pos(1.0, 2.0, 3.0, 4f, 5f) - file.writeText( - """ - { - x: 1.0 - y: 2.0 - z: 3.0 - yaw: 4.0 - pitch: 5.0 - } - """.trimIndent() - ) - - val configFile = ConfigFactory.parseFile(file) - val result: Pos = hocon.decodeFromConfig(configFile) - - assertEquals(expected, result) - } - - } - - @Nested - inner class ReadConfigurationFileWithReifiedTypeParameter { - - @Test - fun `should read configuration file`() { - configurationToHoconFile(expectedDefaultConfiguration, file) - val configuration = HoconConfigurationReader() - val result: TestConfiguration = configuration.readConfigurationFile(file) - assertEquals(expectedDefaultConfiguration, result) - } - - } - - @Nested - inner class ReadConfigurationFileWithClassParameter { - - @Test - fun `should read configuration file`() { - configurationToHoconFile(expectedDefaultConfiguration, file) - val configuration = HoconConfigurationReader() - val result = configuration.readConfigurationFile(TestConfiguration::class, file) - assertEquals(expectedDefaultConfiguration, result) - } - - } - - @Nested - inner class ReadConfigurationFileWithSerializerParameter { - - @Test - fun `should read configuration file`() { - configurationToHoconFile(expectedDefaultConfiguration, file) - val configuration = HoconConfigurationReader() - val result = configuration.readConfigurationFile(TestConfiguration.serializer(), file) - assertEquals(expectedDefaultConfiguration, result) - } - - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/configuration/IConfigurationReaderTest.kt b/src/test/kotlin/com/github/rushyverse/api/configuration/IConfigurationReaderTest.kt deleted file mode 100644 index 063ea48a..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/configuration/IConfigurationReaderTest.kt +++ /dev/null @@ -1,99 +0,0 @@ -@file:OptIn(ExperimentalCoroutinesApi::class) - -package com.github.rushyverse.api.configuration - -import com.github.rushyverse.api.AbstractTest -import com.github.rushyverse.api.configuration.IConfigurationReader.Companion.getOrCreateConfigurationFile -import com.github.rushyverse.api.utils.randomString -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.assertThrows -import java.io.File -import java.io.FileNotFoundException -import java.util.* -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -class IConfigurationReaderTest : AbstractTest() { - - @Test - fun `name of default configuration file is correct`() = runTest { - assertEquals("server.conf", IConfigurationReader.DEFAULT_CONFIG_FILE_NAME) - } - - @Nested - inner class GetOrCreateConfigurationFile { - - @Nested - inner class GetExistingConfigurationFile { - - @Test - fun `should return the given configuration file`() = runTest { - createConfigFileAndCheckIfFound(randomString()) { - getOrCreateConfigurationFile(it.absolutePath) - } - } - - @Test - fun `should return the default config file without edit it`() = runTest { - createConfigFileAndCheckIfFound(IConfigurationReader.DEFAULT_CONFIG_FILE_NAME) { - getOrCreateConfigurationFile() - } - } - - private inline fun createConfigFileAndCheckIfFound(fileName: String, block: (File) -> File) { - val configurationFile = fileOfTmpDirectory(fileName) - assertTrue { configurationFile.createNewFile() } - - val content = UUID.randomUUID().toString() - configurationFile.writeText(content) - - val file = block(configurationFile) - assertEquals(configurationFile, file) - assertEquals(content, file.readText()) - } - } - - @Nested - inner class GetNonExistingConfigurationFile { - - @Test - fun `should throw exception if file not found`() = runTest { - assertThrows { - getOrCreateConfigurationFile(getRandomFileInTmpDirectory().absolutePath) - } - } - - @Test - fun `should throw exception if file is not a regular file`() = runTest { - assertThrows { - getOrCreateConfigurationFile(tmpDirectory.absolutePath) - } - } - } - - @Nested - inner class CreateDefaultConfiguration { - @Test - fun `should create the config file if it's not found in the current directory`() = runTest { - val configurationFile = getOrCreateConfigurationFile() - assertTrue { configurationFile.isFile } - - val expectedConfigurationFile = fileOfTmpDirectory(IConfigurationReader.DEFAULT_CONFIG_FILE_NAME) - assertEquals(expectedConfigurationFile, configurationFile) - - inputStreamOfDefaultConfiguration().bufferedReader().use { - assertEquals(it.readText(), configurationFile.readText()) - } - } - } - - private fun getRandomFileInTmpDirectory() = fileOfTmpDirectory(randomString()) - } - - private fun inputStreamOfDefaultConfiguration() = - IConfiguration::class.java.classLoader.getResourceAsStream(IConfigurationReader.DEFAULT_CONFIG_FILE_NAME) - ?: error("Unable to find default configuration file in server resources") -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/configuration/VelocityConfigurationTest.kt b/src/test/kotlin/com/github/rushyverse/api/configuration/VelocityConfigurationTest.kt deleted file mode 100644 index 60c1092c..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/configuration/VelocityConfigurationTest.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.github.rushyverse.api.configuration - -import com.github.rushyverse.api.utils.randomString -import kotlinx.serialization.json.Json -import org.junit.jupiter.api.Nested -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource -import kotlin.test.assertEquals - -class VelocityConfigurationTest { - - @Nested - inner class Serialize { - - @ValueSource(booleans = [true, false]) - @ParameterizedTest - fun `with empty secret`(enabled: Boolean) { - val configuration = VelocityConfiguration(enabled, "") - val serialize = Json.encodeToString(VelocityConfiguration.serializer(), configuration) - assertEquals("{\"enabled\":$enabled,\"secret\":\"\"}", serialize) - } - - @ValueSource(booleans = [true, false]) - @ParameterizedTest - fun `with non empty secret`(enabled: Boolean) { - val secret = randomString() - val configuration = VelocityConfiguration(enabled, secret) - val serialize = Json.encodeToString(VelocityConfiguration.serializer(), configuration) - assertEquals("{\"enabled\":$enabled,\"secret\":\"$secret\"}", serialize) - } - - } - - @Nested - inner class Deserialize { - - @ValueSource(booleans = [true, false]) - @ParameterizedTest - fun `with empty secret`(enabled: Boolean) { - val string = "{\"enabled\":$enabled,\"secret\":\"\"}" - val deserialize = Json.decodeFromString(VelocityConfiguration.serializer(), string) - assertEquals(VelocityConfiguration(enabled, ""), deserialize) - } - - @ValueSource(booleans = [true, false]) - @ParameterizedTest - fun `with non empty secret`(enabled: Boolean) { - val secret = randomString() - val string = "{\"enabled\":$enabled,\"secret\":\"$secret\"}" - val deserialize = Json.decodeFromString(VelocityConfiguration.serializer(), string) - assertEquals(VelocityConfiguration(enabled, secret), deserialize) - } - - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/coroutine/MinestomAsyncCoroutineDispatcherTest.kt b/src/test/kotlin/com/github/rushyverse/api/coroutine/MinestomAsyncCoroutineDispatcherTest.kt deleted file mode 100644 index 336ada5b..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/coroutine/MinestomAsyncCoroutineDispatcherTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.github.rushyverse.api.coroutine - -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify -import net.minestom.server.ServerProcess -import net.minestom.server.timer.ExecutionType -import net.minestom.server.timer.SchedulerManager -import kotlin.test.Test - -class MinestomAsyncCoroutineDispatcherTest { - - @Test - fun `should not perform if process is not alive`() { - val process = mockk() - every { process.isAlive } returns false - val schedulerManager = mockk() - every { process.scheduler() } returns schedulerManager - - val dispatcher = MinestomAsyncCoroutineDispatcher(process) - dispatcher.dispatch(mockk()) {} - - verify(exactly = 0) { schedulerManager.scheduleNextProcess(any(), any()) } - verify(exactly = 0) { schedulerManager.scheduleNextTick(any()) } - } - - @Test - fun `should perform task in async context`() { - val process = mockk() - every { process.isAlive } returns true - val schedulerManager = mockk() - every { process.scheduler() } returns schedulerManager - every { schedulerManager.scheduleNextProcess(any(), any()) } returns mockk() - - val dispatcher = MinestomAsyncCoroutineDispatcher(process) - - val runnable = mockk() - dispatcher.dispatch(mockk(), runnable) - - verify(exactly = 1) { schedulerManager.scheduleNextProcess(runnable, ExecutionType.ASYNC) } - verify(exactly = 0) { schedulerManager.scheduleNextTick(any()) } - } - -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/coroutine/MinestomCoroutineDispatcherTest.kt b/src/test/kotlin/com/github/rushyverse/api/coroutine/MinestomCoroutineDispatcherTest.kt deleted file mode 100644 index f12c9c70..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/coroutine/MinestomCoroutineDispatcherTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.github.rushyverse.api.coroutine - -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify -import net.minestom.server.ServerProcess -import net.minestom.server.timer.ExecutionType -import net.minestom.server.timer.SchedulerManager -import kotlin.test.Test - -class MinestomCoroutineDispatcherTest { - - @Test - fun `should not perform if process is not alive`() { - val process = mockk() - every { process.isAlive } returns false - val schedulerManager = mockk() - every { process.scheduler() } returns schedulerManager - - val dispatcher = MinestomCoroutineDispatcher(process, mockk()) - dispatcher.dispatch(mockk()) {} - - verify(exactly = 0) { schedulerManager.scheduleNextProcess(any(), any()) } - verify(exactly = 0) { schedulerManager.scheduleNextTick(any()) } - } - - @Test - fun `should perform task in sync context`() { - shouldPerformTask(ExecutionType.SYNC) - } - - @Test - fun `should perform task in async context`() { - shouldPerformTask(ExecutionType.ASYNC) - } - - private fun shouldPerformTask(type: ExecutionType) { - val process = mockk() - every { process.isAlive } returns true - val schedulerManager = mockk() - every { process.scheduler() } returns schedulerManager - every { schedulerManager.scheduleNextProcess(any(), any()) } returns mockk() - - val dispatcher = MinestomCoroutineDispatcher(process, type) - - val runnable = mockk() - dispatcher.dispatch(mockk(), runnable) - - verify(exactly = 1) { schedulerManager.scheduleNextProcess(runnable, type) } - verify(exactly = 0) { schedulerManager.scheduleNextTick(any()) } - } - -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/coroutine/MinestomSyncCoroutineDispatcherTest.kt b/src/test/kotlin/com/github/rushyverse/api/coroutine/MinestomSyncCoroutineDispatcherTest.kt deleted file mode 100644 index fb69a153..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/coroutine/MinestomSyncCoroutineDispatcherTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.github.rushyverse.api.coroutine - -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify -import net.minestom.server.ServerProcess -import net.minestom.server.timer.ExecutionType -import net.minestom.server.timer.SchedulerManager -import kotlin.test.Test - -class MinestomSyncCoroutineDispatcherTest { - - @Test - fun `should not perform if process is not alive`() { - val process = mockk() - every { process.isAlive } returns false - val schedulerManager = mockk() - every { process.scheduler() } returns schedulerManager - - val dispatcher = MinestomSyncCoroutineDispatcher(process) - dispatcher.dispatch(mockk()) {} - - verify(exactly = 0) { schedulerManager.scheduleNextProcess(any(), any()) } - verify(exactly = 0) { schedulerManager.scheduleNextTick(any()) } - } - - @Test - fun `should perform task in sync context`() { - val process = mockk() - every { process.isAlive } returns true - val schedulerManager = mockk() - every { process.scheduler() } returns schedulerManager - every { schedulerManager.scheduleNextProcess(any(), any()) } returns mockk() - - val dispatcher = MinestomSyncCoroutineDispatcher(process) - - val runnable = mockk() - dispatcher.dispatch(mockk(), runnable) - - verify(exactly = 1) { schedulerManager.scheduleNextProcess(runnable, ExecutionType.SYNC) } - verify(exactly = 0) { schedulerManager.scheduleNextTick(any()) } - } - -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/entity/CommonNPCEntityTest.kt b/src/test/kotlin/com/github/rushyverse/api/entity/CommonNPCEntityTest.kt deleted file mode 100644 index 63207f48..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/entity/CommonNPCEntityTest.kt +++ /dev/null @@ -1,307 +0,0 @@ -package com.github.rushyverse.api.entity - -import com.github.rushyverse.api.extension.acquirable -import com.github.rushyverse.api.position.IAreaLocatable -import com.github.rushyverse.api.utils.randomString -import io.mockk.* -import net.minestom.server.coordinate.Pos -import net.minestom.server.entity.Player -import net.minestom.server.event.player.PlayerEntityInteractEvent -import net.minestom.server.thread.Acquirable -import net.minestom.server.thread.Acquired -import net.minestom.testing.Env -import net.minestom.testing.EnvTest -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import kotlin.test.assertEquals - -abstract class CommonNPCEntityTest { - - @Nested - inner class LookNearbyPlayer { - - @Test - fun `should throw exception if area is null`() { - val npc = createEntity(null) - assertThrows { npc.lookNearestPlayer() } - } - - @Test - fun `should keep the position if no player is in area`() { - val area = mockk> { - every { entitiesInArea } returns emptySet() - } - val npc = createEntity(area) - val expectedPos = npc.position - - npc.lookNearestPlayer() - assertEquals(expectedPos, npc.position) - } - - @Test - fun `should look at the player if there is one`() { - val player = mockk() - - val area = mockk> { - every { entitiesInArea } returns setOf(player) - } - val npc = createEntity(area) - - val slotPlayer = slot() - val npcSpy = spyk(npc) { - justRun { lookAt(capture(slotPlayer)) } - } - - npcSpy.lookNearestPlayer() - - verify(exactly = 1) { npcSpy.lookAt(any()) } - assertEquals(player, slotPlayer.captured) - } - - @Test - fun `should look at the nearest player in the list`() { - val player1 = mockPlayerWithAcquirableMocks() - every { player1.position } returns Pos(100.0, 0.0, 0.0) - val player2 = mockPlayerWithAcquirableMocks() - every { player2.position } returns Pos(50.0, 0.0, 0.0) - - val area = mockk> { - every { entitiesInArea } returns setOf(player1, player2) - } - val npc = createEntity(area) - - val slotPlayer = slot() - val npcSpy = spyk(npc) { - justRun { lookAt(capture(slotPlayer)) } - every { position } returns Pos.ZERO - } - - npcSpy.lookNearestPlayer() - - verify(exactly = 1) { npcSpy.lookAt(any()) } - assertEquals(player2, slotPlayer.captured) - } - - } - - private fun mockPlayerWithAcquirableMocks(): Player { - return mockk().also { - val acquired = mockk>() { - every { get() } returns it - justRun { unlock() } - } - val acquirable = mockk>() { - every { lock() } returns acquired - } - every { it.acquirable } returns acquirable - } - } - - @Nested - inner class OnEnterArea { - - @Test - fun `should do nothing`() { - val npc = createEntity(null) - val player = mockk() - npc.onEnterArea(player) - } - - } - - @Nested - inner class OnLeaveArea { - - @Test - fun `should do nothing`() { - val npc = createEntity(null) - val player = mockk() - npc.onLeaveArea(player) - } - - } - - @Nested - inner class OnInteract { - - @Test - fun `should do nothing`() { - val npc = createEntity(null) - val event = mockk() - npc.onInteract(event) - } - - } - - @Nested - @EnvTest - inner class Update { - - @Nested - inner class OnEnterArea { - - @Test - fun `should trigger enter area if new player is in area`(env: Env) { - val pos = Pos(0.0, 0.0, 0.0) - val flatInstance = env.createFlatInstance() - val player = mockk() - val area = mockk> { - justRun { position = any() } - justRun { instance = any() } - every { updateEntitiesInArea() } returns Pair(setOf(player), emptySet()) - } - val npc = createEntity(area) - npc.setInstance(flatInstance, pos) - val npcSpy = spyk(npc) { - justRun { onEnterArea(any()) } - } - npcSpy.update(0) - - verify(exactly = 1) { npcSpy.onEnterArea(player) } - } - - @Test - fun `should not trigger enter area if the player is always in the area`(env: Env) { - val pos = Pos(0.0, 0.0, 0.0) - val flatInstance = env.createFlatInstance() - val player = mockk() - val area = mockk> { - justRun { position = any() } - justRun { instance = any() } - every { updateEntitiesInArea() } returns Pair(setOf(player), emptySet()) - } - val npc = createEntity(area) - npc.setInstance(flatInstance, pos) - val npcSpy = spyk(npc) { - justRun { onEnterArea(any()) } - } - npcSpy.update(0) - - verify(exactly = 1) { npcSpy.onEnterArea(player) } - - every { area.updateEntitiesInArea() } returns Pair(emptySet(), emptySet()) - - npcSpy.update(1) - - verify(exactly = 1) { npcSpy.onEnterArea(player) } - verify(exactly = 0) { npcSpy.onLeaveArea(any()) } - } - - } - - @Nested - inner class OnLeaveArea { - - @Test - fun `should trigger leave area if player is not in area anymore`(env: Env) { - val pos = Pos(0.0, 0.0, 0.0) - val flatInstance = env.createFlatInstance() - val player = mockk() - val area = mockk> { - justRun { position = any() } - justRun { instance = any() } - every { updateEntitiesInArea() } returns Pair(emptySet(), setOf(player)) - } - - val npc = createEntity(area) - npc.setInstance(flatInstance, pos) - val npcSpy = spyk(npc) { - justRun { onLeaveArea(any()) } - } - npcSpy.update(0) - - verify(exactly = 1) { npcSpy.onLeaveArea(player) } - } - - @Test - fun `should not leave enter area if the player is always out of the area`(env: Env) { - val pos = Pos(0.0, 0.0, 0.0) - val flatInstance = env.createFlatInstance() - val player = mockk() - val area = mockk> { - justRun { position = any() } - justRun { instance = any() } - every { updateEntitiesInArea() } returns Pair(emptySet(), setOf(player)) - } - val npc = createEntity(area) - npc.setInstance(flatInstance, pos) - val npcSpy = spyk(npc) { - justRun { onLeaveArea(any()) } - } - npcSpy.update(0) - - verify(exactly = 1) { npcSpy.onLeaveArea(player) } - - every { area.updateEntitiesInArea() } returns Pair(emptySet(), emptySet()) - - npcSpy.update(1) - - verify(exactly = 1) { npcSpy.onLeaveArea(player) } - verify(exactly = 0) { npcSpy.onEnterArea(any()) } - } - - } - - @Test - fun `should update the area entities`(env: Env) { - val pos = Pos(0.0, 0.0, 0.0) - val flatInstance = env.createFlatInstance() - val area = mockk> { - justRun { position = any() } - justRun { instance = any() } - every { updateEntitiesInArea() } returns Pair(emptySet(), emptySet()) - } - val npc = createEntity(area) - npc.setInstance(flatInstance, pos) - npc.update(0) - - verify(exactly = 1) { area.instance = flatInstance } - verify(exactly = 1) { area.position = pos } - verify(exactly = 1) { area.updateEntitiesInArea() } - } - - @Test - fun `should trigger enter and leave area if players is in and out of area`(env: Env) { - val pos = Pos(0.0, 0.0, 0.0) - val flatInstance = env.createFlatInstance() - val player = mockk(randomString()) - val player2 = mockk(randomString()) - val area = mockk> { - justRun { position = any() } - justRun { instance = any() } - every { updateEntitiesInArea() } returns Pair(setOf(player), setOf(player2)) - } - val npc = createEntity(area) - npc.setInstance(flatInstance, pos) - val npcSpy = spyk(npc) { - justRun { onEnterArea(any()) } - justRun { onLeaveArea(any()) } - } - npcSpy.update(0) - - verify(exactly = 1) { npcSpy.onEnterArea(player) } - verify(exactly = 1) { npcSpy.onLeaveArea(player2) } - - every { area.updateEntitiesInArea() } returns Pair(setOf(player2), setOf(player)) - - npcSpy.update(1) - - verify(exactly = 1) { npcSpy.onEnterArea(player) } - verify(exactly = 1) { npcSpy.onEnterArea(player2) } - verify(exactly = 1) { npcSpy.onLeaveArea(player) } - verify(exactly = 1) { npcSpy.onLeaveArea(player2) } - - every { area.updateEntitiesInArea() } returns Pair(emptySet(), emptySet()) - - npcSpy.update(0) - - verify(exactly = 1) { npcSpy.onEnterArea(player) } - verify(exactly = 1) { npcSpy.onEnterArea(player2) } - verify(exactly = 1) { npcSpy.onLeaveArea(player) } - verify(exactly = 1) { npcSpy.onLeaveArea(player2) } - } - } - - protected abstract fun createEntity(area: IAreaLocatable?): NPCEntity -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/entity/NPCEntityTest.kt b/src/test/kotlin/com/github/rushyverse/api/entity/NPCEntityTest.kt deleted file mode 100644 index 78e712f4..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/entity/NPCEntityTest.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.github.rushyverse.api.entity - -import com.github.rushyverse.api.position.IAreaLocatable -import net.minestom.server.entity.EntityType -import net.minestom.server.entity.Player -import org.junit.jupiter.api.Nested -import kotlin.test.Test -import kotlin.test.assertNull - -class NPCEntityTest : CommonNPCEntityTest() { - - @Nested - inner class Instantiation { - - @Test - fun `should set area trigger as null if not defined`() { - val npc = NPCEntity(EntityType.CREEPER) - assertNull(npc.areaTrigger) - } - } - - override fun createEntity(area: IAreaLocatable?): NPCEntity { - return NPCEntity(EntityType.CREEPER, area) - } - -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/entity/PlayerNPCEntityTest.kt b/src/test/kotlin/com/github/rushyverse/api/entity/PlayerNPCEntityTest.kt deleted file mode 100644 index bd6cf8c2..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/entity/PlayerNPCEntityTest.kt +++ /dev/null @@ -1,185 +0,0 @@ -package com.github.rushyverse.api.entity - -import com.github.rushyverse.api.extension.AddPlayerTextureProperty -import com.github.rushyverse.api.position.IAreaLocatable -import com.github.rushyverse.api.utils.randomPos -import com.github.rushyverse.api.utils.randomString -import net.kyori.adventure.text.Component -import net.minestom.server.entity.GameMode -import net.minestom.server.entity.Player -import net.minestom.server.network.packet.server.play.PlayerInfoPacket -import net.minestom.server.network.packet.server.play.PlayerInfoPacket.AddPlayer -import net.minestom.testing.Env -import net.minestom.testing.EnvTest -import org.junit.jupiter.api.Nested -import java.util.* -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNull - - -class PlayerNPCEntityTest : CommonNPCEntityTest() { - - @Nested - inner class Instantiation { - - @Test - fun `should set properties as empty list if not defined`() { - val npc = PlayerNPCEntity(randomString()) - assertEquals(emptyList(), npc.properties) - } - - @Test - fun `should set area trigger as null if not defined`() { - val npc = PlayerNPCEntity(randomString()) - assertNull(npc.areaTrigger) - } - - @Test - fun `should set inTabList as false if not defined`() { - val npc = PlayerNPCEntity(randomString()) - assertFalse { npc.inTabList } - } - } - - @Nested - @EnvTest - inner class UpdateNewViewer { - - @Nested - inner class AddPlayerPacket { - - @Test - fun `should send add player packet to new viewer with default values`(env: Env) { - assertPacketSent( - env, - UUID.randomUUID(), - randomString(), - emptyList(), - null - ) - } - - @Test - fun `should send add player packet to new viewer with textures property`(env: Env) { - assertPacketSent( - env, - UUID.randomUUID(), - randomString(), - listOf( - AddPlayerTextureProperty(randomString(), randomString()) - ), - null - ) - } - - @Test - fun `should send add player packet to new viewer with custom name`(env: Env) { - assertPacketSent( - env, - UUID.randomUUID(), - randomString(), - emptyList(), - Component.text(randomString()) - ) - } - - private fun assertPacketSent( - env: Env, - npcUUID: UUID, - npcName: String, - npcProperties: List, - npcCustomName: Component? - ) { - val instance = env.createFlatInstance() - val connection = env.createConnection() - val player = connection.connect(instance, randomPos()).join() - - val npc = PlayerNPCEntity(npcName, npcProperties, null, npcUUID, false) - npc.customName = npcCustomName - - val packetTracker = connection.trackIncoming(PlayerInfoPacket::class.java) - npc.updateNewViewer(player) - - packetTracker.assertSingle { - assertEquals( - PlayerInfoPacket( - PlayerInfoPacket.Action.ADD_PLAYER, - listOf( - AddPlayer( - npcUUID, - npcName, - npcProperties, - GameMode.CREATIVE, - 0, - npcCustomName ?: Component.text(npcName), - null - ) - ) - ), it - ) - } - } - - } - - @Nested - inner class RemovePlayerPacket { - - @Test - fun `should send remove packet to new viewer`(env: Env) { - assertPacketSent( - env, - UUID.randomUUID(), - false - ) - } - - @Test - fun `should not send remove packet to new viewer`(env: Env) { - assertPacketSent( - env, - UUID.randomUUID(), - true - ) - } - - private fun assertPacketSent( - env: Env, - npcUUID: UUID, - inTabList: Boolean - ) { - val instance = env.createFlatInstance() - val connection = env.createConnection() - val player = connection.connect(instance, randomPos()).join() - - val npc = PlayerNPCEntity(randomString(), inTabList = inTabList, uuid = npcUUID) - - npc.updateNewViewer(player) - - val packetTracker = connection.trackIncoming(PlayerInfoPacket::class.java) - npc.scheduler().processTick() - - if (inTabList) { - packetTracker.assertEmpty() - } else { - packetTracker.assertSingle { - assertEquals( - PlayerInfoPacket( - PlayerInfoPacket.Action.REMOVE_PLAYER, - listOf(PlayerInfoPacket.RemovePlayer(npcUUID)) - ), it - ) - } - } - } - - } - - } - - override fun createEntity(area: IAreaLocatable?): NPCEntity { - return PlayerNPCEntity(randomString(), areaTrigger = area) - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/AcquirableExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/AcquirableExtTest.kt deleted file mode 100644 index 9e550967..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/extension/AcquirableExtTest.kt +++ /dev/null @@ -1,96 +0,0 @@ -package com.github.rushyverse.api.extension - -import io.mockk.every -import io.mockk.mockk -import io.mockk.spyk -import net.minestom.server.entity.Entity -import net.minestom.server.entity.Player -import net.minestom.server.thread.Acquirable -import org.junit.jupiter.api.Nested -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource -import kotlin.test.Test -import kotlin.test.assertContentEquals -import kotlin.test.assertTrue - -class AcquirableExtTest { - - @Test - fun `should get the acquirable of the entity`() { - val entity = mockk() - val acquirable = mockk>() - every { entity.getAcquirable() } returns acquirable - assertTrue { entity.acquirable == acquirable } - } - - @Nested - inner class IterableToAcquirables { - - @Test - fun `empty to AcquirableCollection`() { - val iterable = emptyList() - val acquirableCollection = iterable.toAcquirables() - assertTrue(acquirableCollection.unwrap().toList().isEmpty()) - } - - - @ParameterizedTest - @ValueSource(ints = [1, 5, 10]) - fun `not empty to AcquirableCollection`(numberOfEntities: Int) { - val list = createListOfEntities(numberOfEntities) - val iterable = list.asIterable() - val acquirableCollection = iterable.toAcquirables() - assertContentEquals(iterable, acquirableCollection.unwrap().toList()) - } - } - - @Nested - inner class ArrayToAcquirables { - - @Test - fun `empty to AcquirableCollection`() { - val array = emptyArray() - val acquirableCollection = array.toAcquirables() - assertTrue(acquirableCollection.unwrap().toList().isEmpty()) - } - - - @ParameterizedTest - @ValueSource(ints = [1, 5, 10]) - fun `not empty to AcquirableCollection`(numberOfEntities: Int) { - val array = createListOfEntities(numberOfEntities).toTypedArray() - val acquirableCollection = array.toAcquirables() - assertContentEquals(array.toList(), acquirableCollection.unwrap().toList()) - } - } - - @Nested - inner class SequenceToAcquirables { - - @Test - fun `empty to AcquirableCollection`() { - val array = emptySequence() - val acquirableCollection = array.toAcquirables() - assertTrue(acquirableCollection.unwrap().toList().isEmpty()) - } - - - @ParameterizedTest - @ValueSource(ints = [1, 5, 10]) - fun `not empty to AcquirableCollection`(numberOfEntities: Int) { - val sequence = createListOfEntities(numberOfEntities).asSequence() - val acquirableCollection = sequence.toAcquirables() - assertContentEquals(sequence.toList(), acquirableCollection.unwrap().toList()) - } - } - - private fun createListOfEntities(numberOfEntities: Int) = List(numberOfEntities) { - val entity = mockk() - val acquirable = spyk>(Acquirable.of(entity)) { - every { unwrap() } returns entity - } - every { entity.acquirable } returns acquirable - entity - } - -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/CommandExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/CommandExtTest.kt deleted file mode 100644 index 1a37efb7..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/extension/CommandExtTest.kt +++ /dev/null @@ -1,129 +0,0 @@ -package com.github.rushyverse.api.extension - -import com.github.rushyverse.api.utils.assertCoroutineContextFromScope -import com.github.rushyverse.api.utils.randomString -import io.mockk.mockk -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.yield -import net.minestom.server.command.CommandSender -import net.minestom.server.command.builder.Command -import net.minestom.server.command.builder.CommandContext -import net.minestom.server.command.builder.arguments.ArgumentType -import org.junit.jupiter.api.Test -import java.util.concurrent.CountDownLatch -import kotlin.coroutines.coroutineContext -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNotEquals -import kotlin.test.assertTrue - -class CommandExtTest { - - @Test - fun `should use dispatcher to process set default executor suspend`() { - val command = Command("test") - var executed = false - - val senderMock = mockk() - val contextMock = mockk() - - val scope = CoroutineScope(Dispatchers.Default) - - val currentThread = Thread.currentThread() - val latch = CountDownLatch(1) - - command.setDefaultExecutorSuspend(scope) { sender, context -> - assertCoroutineContextFromScope(scope, coroutineContext) - assertEquals(senderMock, sender) - assertEquals(contextMock, context) - executed = true - - assertEquals(currentThread, Thread.currentThread()) - yield() - assertNotEquals(currentThread, Thread.currentThread()) - latch.countDown() - } - command.defaultExecutor?.apply(senderMock, contextMock) - latch.await() - assertTrue(executed) - } - - @Test - fun `should use dispatcher to process add syntax suspend`() { - val command = Command("test") - val stringArg = ArgumentType.String("string") - val intArg = ArgumentType.Integer("int") - var executed = false - - val senderMock = mockk() - val contextMock = mockk() - val scope = CoroutineScope(Dispatchers.Default) - - val currentThread = Thread.currentThread() - val latch = CountDownLatch(1) - - command.addSyntaxSuspend({ sender, context -> - assertCoroutineContextFromScope(scope, coroutineContext) - assertEquals(senderMock, sender) - assertEquals(contextMock, context) - executed = true - - assertEquals(currentThread, Thread.currentThread()) - yield() - assertNotEquals(currentThread, Thread.currentThread()) - latch.countDown() - }, stringArg, intArg, coroutineScope = scope) - - command.syntaxes.first().executor.apply(senderMock, contextMock) - latch.await() - assertTrue(executed) - } - - @Test - fun `should use dispatcher to process add conditional syntax suspend`() { - val command = Command("test") - val stringArg = ArgumentType.String("string") - val intArg = ArgumentType.Integer("int") - var executed = false - var conditionalExecuted = false - - val senderMock = mockk() - val contextMock = mockk() - val scope = CoroutineScope(Dispatchers.Default) - - val currentThread = Thread.currentThread() - val latch = CountDownLatch(1) - - val expectedCommandString = randomString() - command.addConditionalSyntaxSuspend( - { sender, commandString -> - assertEquals(senderMock, sender) - assertEquals(expectedCommandString, commandString) - conditionalExecuted = true - true - }, - { sender, context -> - assertCoroutineContextFromScope(scope, coroutineContext) - assertEquals(senderMock, sender) - assertEquals(contextMock, context) - executed = true - - assertEquals(currentThread, Thread.currentThread()) - yield() - assertNotEquals(currentThread, Thread.currentThread()) - latch.countDown() - - }, stringArg, intArg, coroutineScope = scope - ) - - val syntax = command.syntaxes.first() - syntax.commandCondition!!.canUse(senderMock, expectedCommandString) - assertFalse(executed) - assertTrue(conditionalExecuted) - - syntax.executor.apply(senderMock, contextMock) - latch.await() - assertTrue(executed) - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/ComponentExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/ComponentExtTest.kt deleted file mode 100644 index 66c5bbf2..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/extension/ComponentExtTest.kt +++ /dev/null @@ -1,297 +0,0 @@ -package com.github.rushyverse.api.extension - -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.event.ClickEvent -import net.kyori.adventure.text.format.NamedTextColor -import net.kyori.adventure.text.format.TextColor -import net.kyori.adventure.text.format.TextDecoration -import org.junit.jupiter.api.Nested -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource -import kotlin.test.Test -import kotlin.test.assertEquals - -class ComponentExtTest { - - @Nested - inner class ChangeDecoration { - - @Nested - inner class Bold { - - @Test - fun `should add bold`() { - assertEquals( - Component.text("Hello").decoration(TextDecoration.BOLD, TextDecoration.State.TRUE), - Component.text("Hello").withBold() - ) - } - - @Test - fun `should remove bold`() { - assertEquals( - Component.text("Hello").decoration(TextDecoration.BOLD, TextDecoration.State.FALSE), - Component.text("Hello").withoutBold() - ) - } - - @Test - fun `should undefine bold`() { - assertEquals( - Component.text("Hello").decoration(TextDecoration.BOLD, TextDecoration.State.NOT_SET), - Component.text("Hello").undefineBold() - ) - } - - } - - @Nested - inner class Italic { - - @Test - fun `should add italic`() { - assertEquals( - Component.text("Hello").decoration(TextDecoration.ITALIC, TextDecoration.State.TRUE), - Component.text("Hello").withItalic() - ) - } - - @Test - fun `should remove italic`() { - assertEquals( - Component.text("Hello").decoration(TextDecoration.ITALIC, TextDecoration.State.FALSE), - Component.text("Hello").withoutItalic() - ) - } - - @Test - fun `should undefine italic`() { - assertEquals( - Component.text("Hello").decoration(TextDecoration.ITALIC, TextDecoration.State.NOT_SET), - Component.text("Hello").undefineItalic() - ) - } - - } - - @Nested - inner class Underlined { - - @Test - fun `should add underlined`() { - assertEquals( - Component.text("Hello").decoration(TextDecoration.UNDERLINED, TextDecoration.State.TRUE), - Component.text("Hello").withUnderlined() - ) - } - - @Test - fun `should remove underlined`() { - assertEquals( - Component.text("Hello").decoration(TextDecoration.UNDERLINED, TextDecoration.State.FALSE), - Component.text("Hello").withoutUnderlined() - ) - } - - @Test - fun `should undefine underlined`() { - assertEquals( - Component.text("Hello").decoration(TextDecoration.UNDERLINED, TextDecoration.State.NOT_SET), - Component.text("Hello").undefineUnderlined() - ) - } - - } - - @Nested - inner class Strikethrough { - - @Test - fun `should add strikethrough`() { - assertEquals( - Component.text("Hello").decoration(TextDecoration.STRIKETHROUGH, TextDecoration.State.TRUE), - Component.text("Hello").withStrikethrough() - ) - } - - @Test - fun `should remove strikethrough`() { - assertEquals( - Component.text("Hello").decoration(TextDecoration.STRIKETHROUGH, TextDecoration.State.FALSE), - Component.text("Hello").withoutStrikethrough() - ) - } - - @Test - fun `should undefine strikethrough`() { - assertEquals( - Component.text("Hello").decoration(TextDecoration.STRIKETHROUGH, TextDecoration.State.NOT_SET), - Component.text("Hello").undefineStrikethrough() - ) - } - - } - - @Nested - inner class Obfuscated { - - @Test - fun `should add obfuscated`() { - assertEquals( - Component.text("Hello").decoration(TextDecoration.OBFUSCATED, TextDecoration.State.TRUE), - Component.text("Hello").withObfuscated() - ) - } - - @Test - fun `should remove obfuscated`() { - assertEquals( - Component.text("Hello").decoration(TextDecoration.OBFUSCATED, TextDecoration.State.FALSE), - Component.text("Hello").withoutObfuscated() - ) - } - - @Test - fun `should undefine obfuscated`() { - assertEquals( - Component.text("Hello").decoration(TextDecoration.OBFUSCATED, TextDecoration.State.NOT_SET), - Component.text("Hello").undefineObfuscated() - ) - } - - } - - @Nested - inner class Decorations { - - @Test - fun `should add all decorations`() { - assertEquals( - Component.text("Hello") - .decoration(TextDecoration.BOLD, TextDecoration.State.TRUE) - .decoration(TextDecoration.ITALIC, TextDecoration.State.TRUE) - .decoration(TextDecoration.UNDERLINED, TextDecoration.State.TRUE) - .decoration(TextDecoration.STRIKETHROUGH, TextDecoration.State.TRUE) - .decoration(TextDecoration.OBFUSCATED, TextDecoration.State.TRUE), - Component.text("Hello").withDecorations() - ) - } - - @Test - fun `should remove all decorations`() { - assertEquals( - Component.text("Hello") - .decoration(TextDecoration.BOLD, TextDecoration.State.FALSE) - .decoration(TextDecoration.ITALIC, TextDecoration.State.FALSE) - .decoration(TextDecoration.UNDERLINED, TextDecoration.State.FALSE) - .decoration(TextDecoration.STRIKETHROUGH, TextDecoration.State.FALSE) - .decoration(TextDecoration.OBFUSCATED, TextDecoration.State.FALSE), - Component.text("Hello").withoutDecorations() - ) - } - - @Test - fun `should undefine all decorations`() { - assertEquals( - Component.text("Hello") - .decoration(TextDecoration.BOLD, TextDecoration.State.NOT_SET) - .decoration(TextDecoration.ITALIC, TextDecoration.State.NOT_SET) - .decoration(TextDecoration.UNDERLINED, TextDecoration.State.NOT_SET) - .decoration(TextDecoration.STRIKETHROUGH, TextDecoration.State.NOT_SET) - .decoration(TextDecoration.OBFUSCATED, TextDecoration.State.NOT_SET), - Component.text("Hello").undefineDecorations() - ) - } - - } - } - - @Nested - inner class StringToComponent { - - @Test - fun `should return empty component if empty string`() { - val component = "".toComponent() - assertEquals(Component.empty(), component) - } - - @ParameterizedTest - @ValueSource(strings = ["Hello", "b81f1Hello", "Hello https://www.youtube.com #3b81f1"]) - fun `should return component with text without extract color`(value: String) { - val component = value.toComponent(extractColors = false) - assertEquals(Component.text(value), component) - } - - @Test - fun `should separate text and url`() { - val component = - "Hello https://www.youtube.com b81f1".toComponent(extractUrls = true, extractColors = false) - val expected = Component.text("Hello ") - .append( - Component.text("https://www.youtube.com").clickEvent(ClickEvent.openUrl("https://www.youtube.com")) - ) - .append(Component.text(" b81f1")) - assertEquals(expected, component) - } - - @Test - fun `should return component with text with legacy color`() { - val component = "&cHello".toComponent() - assertEquals(Component.text("Hello").color(NamedTextColor.RED), component) - } - - @Test - fun `should return component with text with hex color`() { - val component = "b81f1Hello".toComponent() - assertEquals(Component.text("Hello").color(TextColor.fromHexString("#3b81f1")), component) - } - - @Test - fun `should return component with text if legacy colors is disabled`() { - val component = "&cHello".toComponent(extractColors = false) - assertEquals(Component.text("&cHello"), component) - } - - @Test - fun `should return component with text using another color character`() { - val component = "^#3b81f1Hello".toComponent(colorChar = '^') - assertEquals(Component.text("Hello").color(TextColor.fromHexString("#3b81f1")), component) - } - } - - @Nested - inner class ComponentToText { - - @Test - fun `should return empty string if empty component`() { - val text = Component.empty().toText() - assertEquals("", text) - } - - @Test - fun `should return text without legacy color`() { - val text = Component.text("Hello").toText() - assertEquals("Hello", text) - } - - @Test - fun `should return text with legacy color`() { - val text = Component.text("Hello").color(NamedTextColor.RED).toText() - assertEquals("§cHello", text) - } - - @Test - fun `should return text with hex color`() { - val text = Component.text("Hello").color(TextColor.fromHexString("#3b81f1")).toText() - assertEquals("§9Hello", text) - } - - @Test - fun `should return text with url`() { - val text = Component.text("Hello ").append( - Component.text("https://www.youtube.com").clickEvent(ClickEvent.openUrl("https://www.youtube.com")) - ).toText() - assertEquals("Hello https://www.youtube.com", text) - } - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/EntityExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/EntityExtTest.kt deleted file mode 100644 index 5bd84b02..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/extension/EntityExtTest.kt +++ /dev/null @@ -1,237 +0,0 @@ -package com.github.rushyverse.api.extension - -import com.github.rushyverse.api.utils.assertCoroutineContextFromScope -import com.github.rushyverse.api.utils.randomString -import io.mockk.every -import io.mockk.justRun -import io.mockk.mockk -import io.mockk.verify -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.test.runTest -import net.minestom.server.entity.Entity -import net.minestom.server.entity.Player -import net.minestom.server.event.EventFilter -import net.minestom.server.event.EventListener -import net.minestom.server.event.EventNode -import net.minestom.server.event.entity.EntityAttackEvent -import net.minestom.server.event.entity.EntityDeathEvent -import net.minestom.server.event.item.ItemDropEvent -import net.minestom.server.event.trait.EntityEvent -import net.minestom.server.thread.Acquirable -import net.minestom.server.thread.Acquired -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.assertThrows -import java.util.concurrent.CountDownLatch -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class EntityExtTest { - - @Nested - inner class Sync { - - @Test - fun `should lock entity`() = runTest { - val player = mockk() - val acquired = mockk>() { - every { get() } returns player - justRun { unlock() } - } - val acquirable = mockk>() { - every { lock() } returns acquired - } - - every { player.acquirable } returns acquirable - - var executed = false - val expectedValue = randomString() - val returnedValue = player.sync { - verify(exactly = 1) { player.acquirable } - verify(exactly = 1) { acquired.get() } - verify(exactly = 1) { acquirable.lock() } - verify(exactly = 0) { acquired.unlock() } - executed = true - expectedValue - } - - assertTrue(executed) - assertEquals(expectedValue, returnedValue) - - verify(exactly = 1) { player.acquirable } - verify(exactly = 1) { acquired.get() } - verify(exactly = 1) { acquirable.lock() } - verify(exactly = 1) { acquired.unlock() } - } - - @Test - fun `should unlock entity despite exception`() = runTest { - val player = mockk() - val acquired = mockk>() { - every { get() } returns player - justRun { unlock() } - } - val acquirable = mockk>() { - every { lock() } returns acquired - } - - every { player.acquirable } returns acquirable - - val ex = assertThrows { - player.sync { - throw Exception("Test") - } - } - - assertTrue(ex.message == "Test") - - verify(exactly = 1) { player.acquirable } - verify(exactly = 1) { acquired.get() } - verify(exactly = 1) { acquirable.lock() } - verify(exactly = 1) { acquired.unlock() } - } - } - - @Nested - inner class Async { - - @Test - fun `should lock entity and execute in coroutine`() = runTest { - val player = mockk() - val acquired = mockk>() { - every { get() } returns player - justRun { unlock() } - } - val acquirable = mockk>() { - every { lock() } returns acquired - } - - every { player.acquirable } returns acquirable - val scope = CoroutineScope(Dispatchers.Default) - - val latch = CountDownLatch(1) - var executed = false - val expectedValue = randomString() - val deferred = player.async(scope) { - assertCoroutineContextFromScope(scope, coroutineContext) - verify(exactly = 1) { player.acquirable } - verify(exactly = 1) { acquired.get() } - verify(exactly = 1) { acquirable.lock() } - verify(exactly = 0) { acquired.unlock() } - - latch.await() - executed = true - expectedValue - } - - verify(exactly = 1) { player.acquirable } - verify(exactly = 1) { acquired.get() } - verify(exactly = 1) { acquirable.lock() } - verify(exactly = 0) { acquired.unlock() } - - assertFalse(executed) - latch.countDown() - assertEquals(expectedValue, deferred.await()) - assertTrue(executed) - - verify(exactly = 1) { player.acquirable } - verify(exactly = 1) { acquired.get() } - verify(exactly = 1) { acquirable.lock() } - verify(exactly = 1) { acquired.unlock() } - } - - @Test - fun `should unlock entity despite exception`() = runTest { - val player = mockk() - val acquired = mockk>() { - every { get() } returns player - justRun { unlock() } - } - val acquirable = mockk>() { - every { lock() } returns acquired - } - - every { player.acquirable } returns acquirable - val scope = CoroutineScope(Dispatchers.Default) - - val deferred = player.async(scope) { - throw Exception("Test") - } - - val ex = try { - deferred.await() - } catch (ex: Exception) { - ex - } - - assertTrue(ex is Exception) - assertTrue(ex.message == "Test") - - verify(exactly = 1) { player.acquirable } - verify(exactly = 1) { acquired.get() } - verify(exactly = 1) { acquirable.lock() } - verify(exactly = 1) { acquired.unlock() } - } - } - - @Nested - inner class OnEvent { - - @Test - fun `should register new event listener on the entity event node`() { - val node = mockk>() { - every { addListener(any()) } returns this - } - val entity = mockk() { - every { eventNode() } returns node - } - - val listener = entity.onEvent { - error("Should not be called") - } - - verify(exactly = 1) { entity.eventNode() } - verify(exactly = 1) { node.addListener(listener) } - } - - @Test - fun `should call body when event is fired`() { - val node = EventNode.type("test", EventFilter.ENTITY) - val entity = mockk() { - every { eventNode() } returns node - } - - val event = mockk() - var called = false - entity.onEvent { - called = true - assertEquals(event, it) - EventListener.Result.SUCCESS - } - - node.call(event) - assertTrue(called) - } - - @Test - fun `should not call body when another event type is fired`() { - val node = EventNode.type("test", EventFilter.ENTITY) - val entity = mockk() { - every { eventNode() } returns node - } - - val event = mockk() - var called = false - entity.onEvent { - called = true - EventListener.Result.SUCCESS - } - - node.call(event) - assertFalse(called) - } - } - -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/InventoryExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/InventoryExtTest.kt deleted file mode 100644 index 38060667..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/extension/InventoryExtTest.kt +++ /dev/null @@ -1,1210 +0,0 @@ -package com.github.rushyverse.api.extension - -import com.github.rushyverse.api.item.ItemComparator -import com.github.rushyverse.api.utils.assertCoroutineContextFromScope -import com.github.rushyverse.api.utils.randomPos -import com.github.rushyverse.api.utils.randomString -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.yield -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.format.NamedTextColor -import net.kyori.adventure.text.format.TextDecoration -import net.minestom.server.inventory.AbstractInventory -import net.minestom.server.inventory.Inventory -import net.minestom.server.inventory.InventoryType -import net.minestom.server.inventory.click.ClickType -import net.minestom.server.inventory.condition.InventoryCondition -import net.minestom.server.item.ItemStack -import net.minestom.server.item.Material -import net.minestom.testing.Env -import net.minestom.testing.EnvTest -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Timeout -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit -import kotlin.coroutines.coroutineContext -import kotlin.test.* - -@Timeout(5, unit = TimeUnit.SECONDS) -class InventoryExtTest { - - @Test - fun `slots property should return the range of slots`() { - val inventory: AbstractInventory = Inventory(InventoryType.CHEST_1_ROW, "Test") - assertEquals(0..8, inventory.slots) - } - - @Nested - inner class RemoveCondition { - - @Test - fun `should remove the condition of interaction with the inventory`() { - val inventory: AbstractInventory = Inventory(InventoryType.CHEST_1_ROW, "Test") - val condition = InventoryCondition { _, _, _, _ -> } - inventory.addInventoryCondition(condition) - assertTrue(inventory.removeCondition(condition)) - } - - @Test - fun `should return false if the condition is not present`() { - val inventory: AbstractInventory = Inventory(InventoryType.CHEST_1_ROW, "Test") - val condition = InventoryCondition { _, _, _, _ -> } - assertFalse(inventory.removeCondition(condition)) - } - - } - - @EnvTest - @Nested - inner class LockItemPosition { - - @Test - fun `should lock the item position`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - val condition = inventory.lockItemPositions() - inventory.setItemStack(0, ItemStack.of(Material.DIAMOND)) - - inventory.slots.forEach { - assertFalse(inventory.leftClick(player, it)) - assertFalse(inventory.rightClick(player, it)) - } - - player.inventory.removeCondition(condition) - - inventory.slots.forEach { - assertTrue(inventory.leftClick(player, it)) - assertTrue(inventory.rightClick(player, it)) - } - } - } - - @EnvTest - @Nested - inner class AddInventoryConditionSuspend { - - @Test - fun `should register a suspendable condition`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - var count = 0 - inventory.addInventoryConditionSuspend { playerClicker, clickedSlot, type, _ -> - assertEquals(player, playerClicker) - assertEquals(0, clickedSlot) - if (count == 0) { - assertEquals(ClickType.LEFT_CLICK, type) - } else { - assertEquals(ClickType.RIGHT_CLICK, type) - } - count++ - } - - assertEquals(1, inventory.inventoryConditions.size) - - inventory.setItemStack(0, ItemStack.of(Material.DIAMOND)) - - val playerSlot = 36 - assertEquals(0, count) - - assertTrue(inventory.leftClick(player, playerSlot)) - assertEquals(1, count) - - assertTrue(inventory.rightClick(player, playerSlot)) - assertEquals(2, count) - } - - @Test - fun `should stay in current thread before suspend point`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - var isCalled = false - val thread = Thread.currentThread().id - inventory.addInventoryConditionSuspend(CoroutineScope(Dispatchers.Default)) { _, _, _, _ -> - assertEquals(thread, Thread.currentThread().id) - isCalled = true - } - - assertTrue(inventory.leftClick(player, 0)) - assertTrue(isCalled) - } - - @Test - fun `should change thread context after suspend point`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val latch = CountDownLatch(1) - val coroutineScope = CoroutineScope(Dispatchers.Default) - - inventory.addInventoryConditionSuspend(coroutineScope) { _, _, _, _ -> - yield() - assertCoroutineContextFromScope(coroutineScope, coroutineContext) - latch.countDown() - } - - assertTrue(inventory.leftClick(player, 0)) - latch.await() - } - } - - @EnvTest - @Nested - inner class RegisterClickEventOnSlotSuspend { - - @Test - fun `should register a click event on a slot`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val slot = 0 - var count = 0 - inventory.registerClickEventOnSlotSuspend(slot) { _, _, _, _ -> - count++ - } - assertEquals(1, inventory.inventoryConditions.size) - - inventory.setItemStack(slot, ItemStack.of(Material.DIAMOND)) - - val playerSlot = 36 - assertEquals(0, count) - - assertTrue(inventory.leftClick(player, playerSlot)) - assertEquals(1, count) - - assertTrue(inventory.rightClick(player, playerSlot)) - assertEquals(2, count) - - assertTrue(inventory.leftClick(player, playerSlot + 1)) - assertEquals(2, count) - } - - @Test - fun `should stay in current thread before suspend point`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - var isCalled = false - val thread = Thread.currentThread().id - inventory.registerClickEventOnSlotSuspend(0, CoroutineScope(Dispatchers.Default)) { _, _, _, _ -> - assertEquals(thread, Thread.currentThread().id) - isCalled = true - } - - assertTrue(inventory.leftClick(player, 36)) - assertTrue(isCalled) - } - - @Test - fun `should change thread context after suspend point`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val latch = CountDownLatch(1) - val coroutineScope = CoroutineScope(Dispatchers.Default) - - inventory.registerClickEventOnSlotSuspend(0, coroutineScope) { _, _, _, _ -> - yield() - assertCoroutineContextFromScope(coroutineScope, coroutineContext) - latch.countDown() - } - - assertTrue(inventory.leftClick(player, 36)) - latch.await() - } - - } - - @EnvTest - @Nested - inner class RegisterClickEventOnSlot { - - @Test - fun `should register a click event on a slot`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val slot = 0 - var count = 0 - inventory.registerClickEventOnSlot(slot) { _, _, _, _ -> - count++ - } - assertEquals(1, inventory.inventoryConditions.size) - - inventory.setItemStack(slot, ItemStack.of(Material.DIAMOND)) - - val playerSlot = 36 - assertEquals(0, count) - - assertTrue(inventory.leftClick(player, playerSlot)) - assertEquals(1, count) - - assertTrue(inventory.rightClick(player, playerSlot)) - assertEquals(2, count) - - assertTrue(inventory.leftClick(player, playerSlot + 1)) - assertEquals(2, count) - } - - } - - @EnvTest - @Nested - inner class RegisterClickEventOnItemSuspend { - - @Test - fun `default identifier should be equals`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.DIAMOND) - var count = 0 - inventory.registerClickEventOnItemSuspend( - item, - coroutineScope = CoroutineScope(Dispatchers.Default) - ) { _, _, _, _ -> - count++ - } - - val playerSlot = 36 - inventory.setItemStack(0, item) - assertEquals(0, count) - - assertTrue(inventory.leftClick(player, playerSlot)) - assertEquals(1, count) - - inventory.setItemStack(0, item.withAmount(2)) - assertTrue(inventory.rightClick(player, playerSlot)) - assertEquals(1, count) - - inventory.setItemStack(1, ItemStack.of(Material.DIAMOND)) - assertTrue(inventory.leftClick(player, playerSlot + 1)) - assertEquals(2, count) - } - - @Test - fun `should trigger the click handler when the item is equals`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.DIAMOND) - var count = 0 - inventory.registerClickEventOnItemSuspend( - item, - ItemComparator.EQUALS, - CoroutineScope(Dispatchers.Default) - ) { _, _, _, _ -> - count++ - } - assertEquals(1, inventory.inventoryConditions.size) - - inventory.setItemStack(0, item) - inventory.setItemStack(1, item) - inventory.setItemStack(3, item) - - assertEquals(0, count) - - assertTrue(inventory.leftClick(player, 36)) // remove the item - assertEquals(1, count) - - assertTrue(inventory.rightClick(player, 37)) - assertEquals(2, count) - - assertTrue(inventory.leftClick(player, 38)) - assertEquals(2, count) - - assertTrue(inventory.leftClick(player, 39)) - assertEquals(3, count) - } - - @Test - fun `should trigger the click handler when the item is similar`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.DIAMOND) - var clicked = false - inventory.registerClickEventOnItemSuspend( - item, - ItemComparator.SIMILAR, - CoroutineScope(Dispatchers.Default) - ) { playerClicked, slot, type, _ -> - assertEquals(player, playerClicked) - assertEquals(0, slot) - assertEquals(ClickType.LEFT_CLICK, type) - clicked = true - } - - val item2 = item.withAmount(20) - inventory.setItemStack(0, item2) - assertTrue(inventory.leftClick(player, 36)) - assertTrue(clicked) - } - - @Test - fun `should stay in current thread before suspend point`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - var isCalled = false - val thread = Thread.currentThread().id - val item = ItemStack.of(Material.DIAMOND) - inventory.registerClickEventOnItemSuspend( - item, - coroutineScope = CoroutineScope(Dispatchers.Default) - ) { _, _, _, _ -> - assertEquals(thread, Thread.currentThread().id) - isCalled = true - } - - inventory.setItemStack(0, item) - assertTrue(inventory.leftClick(player, 36)) - assertTrue(isCalled) - } - - @Test - fun `should change thread context after suspend point`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val latch = CountDownLatch(1) - val coroutineScope = CoroutineScope(Dispatchers.Default) - - val item = ItemStack.of(Material.DIAMOND) - inventory.registerClickEventOnItemSuspend(item, coroutineScope = coroutineScope) { _, _, _, _ -> - yield() - assertCoroutineContextFromScope(coroutineScope, coroutineContext) - latch.countDown() - } - - inventory.setItemStack(0, item) - assertTrue(inventory.leftClick(player, 36)) - latch.await() - } - } - - @EnvTest - @Nested - inner class RegisterClickEventOnItem { - - @Test - fun `default identifier should be equals`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.DIAMOND) - var count = 0 - inventory.registerClickEventOnItem(item) { _, _, _, _ -> - count++ - } - - val playerSlot = 36 - inventory.setItemStack(0, item) - assertEquals(0, count) - - assertTrue(inventory.leftClick(player, playerSlot)) - assertEquals(1, count) - - inventory.setItemStack(0, item.withAmount(2)) - assertTrue(inventory.rightClick(player, playerSlot)) - assertEquals(1, count) - - inventory.setItemStack(1, ItemStack.of(Material.DIAMOND)) - assertTrue(inventory.leftClick(player, playerSlot + 1)) - assertEquals(2, count) - } - - @Test - fun `should trigger the click handler when the item is equals`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.DIAMOND) - var count = 0 - inventory.registerClickEventOnItem(item, ItemComparator.EQUALS) { _, _, _, _ -> - count++ - } - assertEquals(1, inventory.inventoryConditions.size) - - inventory.setItemStack(0, item) - inventory.setItemStack(1, item) - inventory.setItemStack(3, item) - - assertEquals(0, count) - - assertTrue(inventory.leftClick(player, 36)) // remove the item - assertEquals(1, count) - - assertTrue(inventory.rightClick(player, 37)) - assertEquals(2, count) - - assertTrue(inventory.leftClick(player, 38)) - assertEquals(2, count) - - assertTrue(inventory.leftClick(player, 39)) - assertEquals(3, count) - } - - @Test - fun `should trigger the click handler when the item is similar`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.DIAMOND) - var clicked = false - inventory.registerClickEventOnItem(item, ItemComparator.SIMILAR) { playerClicked, slot, type, _ -> - assertEquals(player, playerClicked) - assertEquals(0, slot) - assertEquals(ClickType.LEFT_CLICK, type) - clicked = true - } - - val item2 = item.withAmount(20) - inventory.setItemStack(0, item2) - assertTrue(inventory.leftClick(player, 36)) - assertTrue(clicked) - } - } - - @EnvTest - @Nested - inner class SetItemStackSuspendWithClickHandler { - - @Test - fun `default identifier should be equals`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.DIAMOND) - val slot = 0 - var count = 0 - inventory.setItemStackSuspend( - slot, - item, - coroutineScope = CoroutineScope(Dispatchers.Default) - ) { _, _, _, _ -> - count++ - } - - val playerSlot = 36 - assertEquals(item, inventory.getItemStack(slot)) - assertEquals(1, inventory.itemStacks.filterNot { it.isAir }.size) - - assertTrue(inventory.leftClick(player, playerSlot)) - assertEquals(1, count) - - inventory.setItemStack(slot, item.withAmount(2)) - assertTrue(inventory.leftClick(player, playerSlot)) - assertEquals(1, count) - - inventory.setItemStack(slot + 1, ItemStack.of(Material.DIAMOND)) - assertTrue(inventory.leftClick(player, playerSlot + 1)) - assertEquals(2, count) - } - - @Test - fun `should trigger the click handler when the item is equals`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.DIAMOND) - val slot = 0 - var count = 0 - inventory.setItemStackSuspend( - slot, - item, - ItemComparator.EQUALS, - coroutineScope = CoroutineScope(Dispatchers.Default) - ) { _, _, _, _ -> - count++ - } - - val playerSlot = 36 - assertEquals(1, inventory.inventoryConditions.size) - assertEquals(item, inventory.getItemStack(slot)) - assertEquals(1, inventory.itemStacks.filterNot { it.isAir }.size) - - assertTrue(inventory.leftClick(player, playerSlot)) - assertEquals(1, count) - - inventory.setItemStack(slot + 1, item) - assertTrue(inventory.leftClick(player, playerSlot + 1)) - assertEquals(2, count) - } - - @Test - fun `should trigger the click handler when the item is similar`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.ARROW) - val slot = 0 - var clicked = false - inventory.setItemStackSuspend( - slot, - item, - ItemComparator.SIMILAR, - coroutineScope = CoroutineScope(Dispatchers.Default) - ) { playerClicked, clickedSlot, type, _ -> - assertEquals(player, playerClicked) - assertEquals(slot, clickedSlot) - assertEquals(ClickType.LEFT_CLICK, type) - clicked = true - } - - val item2 = item.withAmount(10) - inventory.setItemStack(slot, item2) - - assertTrue(inventory.leftClick(player, 36)) - assertTrue(clicked) - } - - @Test - fun `should stay in current thread before suspend point`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.ARROW) - val slot = 0 - var clicked = false - val thread = Thread.currentThread().id - inventory.setItemStackSuspend( - slot, - item, - coroutineScope = CoroutineScope(Dispatchers.Default) - ) { _, _, _, _ -> - assertEquals(thread, Thread.currentThread().id) - clicked = true - } - - assertTrue(inventory.leftClick(player, 36)) - assertTrue(clicked) - } - - @Test - fun `should change thread context after suspend point`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.ARROW) - val slot = 0 - - val latch = CountDownLatch(1) - val coroutineScope = CoroutineScope(Dispatchers.Default) - - inventory.setItemStackSuspend(slot, item, coroutineScope = coroutineScope) { _, _, _, _ -> - yield() - assertCoroutineContextFromScope(coroutineScope, coroutineContext) - latch.countDown() - } - - assertTrue(inventory.leftClick(player, 36)) - latch.await() - } - } - - @EnvTest - @Nested - inner class SetItemStackWithClickHandler { - - @Test - fun `default identifier should be equals`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.DIAMOND) - val slot = 0 - var count = 0 - inventory.setItemStack(slot, item) { _, _, _, _ -> - count++ - } - - val playerSlot = 36 - assertEquals(item, inventory.getItemStack(slot)) - assertEquals(1, inventory.itemStacks.filterNot { it.isAir }.size) - - assertTrue(inventory.leftClick(player, playerSlot)) - assertEquals(1, count) - - inventory.setItemStack(slot, item.withAmount(2)) - assertTrue(inventory.leftClick(player, playerSlot)) - assertEquals(1, count) - - inventory.setItemStack(slot + 1, ItemStack.of(Material.DIAMOND)) - assertTrue(inventory.leftClick(player, playerSlot + 1)) - assertEquals(2, count) - } - - @Test - fun `should trigger the click handler when the item is equals`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.DIAMOND) - val slot = 0 - var count = 0 - inventory.setItemStack(slot, item, ItemComparator.EQUALS) { _, _, _, _ -> - count++ - } - - val playerSlot = 36 - assertEquals(1, inventory.inventoryConditions.size) - assertEquals(item, inventory.getItemStack(slot)) - assertEquals(1, inventory.itemStacks.filterNot { it.isAir }.size) - - assertTrue(inventory.leftClick(player, playerSlot)) - assertEquals(1, count) - - inventory.setItemStack(slot + 1, item) - assertTrue(inventory.leftClick(player, playerSlot + 1)) - assertEquals(2, count) - } - - @Test - fun `should trigger the click handler when the item is similar`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.ARROW) - val slot = 0 - var clicked = false - inventory.setItemStack(slot, item, ItemComparator.SIMILAR) { playerClicked, clickedSlot, type, _ -> - assertEquals(player, playerClicked) - assertEquals(slot, clickedSlot) - assertEquals(ClickType.LEFT_CLICK, type) - clicked = true - } - - val item2 = item.withAmount(10) - inventory.setItemStack(slot, item2) - - assertTrue(inventory.leftClick(player, 36)) - assertTrue(clicked) - } - } - - @EnvTest - @Nested - inner class SlotIsEmpty { - - @Test - fun `should return true if the slot is empty`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - assertTrue(inventory.slotIsEmpty(0)) - } - - @Test - fun `should return false if the slot is not empty`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - inventory.setItemStack(0, ItemStack.of(Material.DIAMOND)) - assertFalse(inventory.slotIsEmpty(0)) - } - } - - @EnvTest - @Nested - inner class FirstAvailableSlot { - - @Test - fun `should return the first slot if all slots are empty`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - assertEquals(0, inventory.firstAvailableSlot()) - } - - @Test - fun `should return the first available slot`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - inventory.setItemStack(0, ItemStack.of(Material.DIAMOND)) - assertEquals(1, inventory.firstAvailableSlot()) - } - - @Test - fun `should return -1 if all slots are full`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - inventory.slots.forEach { inventory.setItemStack(it, ItemStack.of(Material.DIAMOND)) } - assertEquals(-1, inventory.firstAvailableSlot()) - } - } - - @EnvTest - @Nested - inner class AddItemStackSuspendWithClickHandler { - - @Test - fun `default identifier should be equals`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.DIAMOND) - var count = 0 - val condition = inventory.addItemStackSuspend( - item, - coroutineScope = CoroutineScope(Dispatchers.Default) - ) { _, _, _, _ -> - count++ - } - - assertNotNull(condition) - assertEquals(1, inventory.inventoryConditions.size) - assertEquals(1, inventory.itemStacks.filterNot { it.isAir }.size) - - val playerSlot = 36 - assertEquals(item, inventory.getItemStack(0)) - - assertTrue(inventory.leftClick(player, playerSlot)) - assertEquals(1, count) - - inventory.setItemStack(0, ItemStack.AIR) - inventory.addItemStack(item.withAmount(2)) - - assertTrue(inventory.leftClick(player, playerSlot)) - assertEquals(1, count) - - inventory.setItemStack(0, ItemStack.AIR) - inventory.addItemStack(ItemStack.of(Material.DIAMOND)) - assertTrue(inventory.leftClick(player, playerSlot)) - assertEquals(2, count) - } - - @Test - fun `should trigger the click handler when the item is equals`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.DIAMOND) - val slot = 0 - var count = 0 - val condition = inventory.addItemStackSuspend( - item, - ItemComparator.EQUALS, - coroutineScope = CoroutineScope(Dispatchers.Default) - ) { _, _, _, _ -> - count++ - } - - assertNotNull(condition) - assertEquals(1, inventory.inventoryConditions.size) - assertEquals(1, inventory.itemStacks.filterNot { it.isAir }.size) - - val playerSlot = 36 - assertEquals(item, inventory.getItemStack(slot)) - assertTrue(inventory.leftClick(player, playerSlot)) - assertEquals(1, count) - - inventory.setItemStack(slot + 1, item) - assertTrue(inventory.leftClick(player, playerSlot + 1)) - assertEquals(2, count) - } - - @Test - fun `should trigger the click handler when the item is similar`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.ARROW) - var clicked = false - val condition = inventory.addItemStackSuspend( - item, - ItemComparator.SIMILAR, - coroutineScope = CoroutineScope(Dispatchers.Default) - ) { _, _, _, _ -> - clicked = true - } - - assertNotNull(condition) - - val item2 = item.withAmount(10) - inventory.setItemStack(1, item2) - - assertTrue(inventory.leftClick(player, 37)) - assertTrue(clicked) - } - - @Test - fun `should not add item and create condition if item can't be added`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.ARROW) - inventory.slots.forEach { inventory.setItemStack(it, item) } - - val diamondItem = ItemStack.of(Material.DIAMOND) - val condition = inventory.addItemStackSuspend( - diamondItem, - coroutineScope = CoroutineScope(Dispatchers.Default) - ) { _, _, _, _ -> } - assertNull(condition) - inventory.itemStacks.forEach { assertNotEquals(diamondItem, it) } - } - - @Test - fun `should stay in current thread before suspend point`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.ARROW) - var clicked = false - val thread = Thread.currentThread().id - inventory.addItemStackSuspend(item, coroutineScope = CoroutineScope(Dispatchers.Default)) { _, _, _, _ -> - assertEquals(thread, Thread.currentThread().id) - clicked = true - } - - assertTrue(inventory.leftClick(player, 36)) - assertTrue(clicked) - } - - @Test - fun `should change thread context after suspend point`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.ARROW) - - val latch = CountDownLatch(1) - val coroutineScope = CoroutineScope(Dispatchers.Default) - - inventory.addItemStackSuspend(item, coroutineScope = coroutineScope) { _, _, _, _ -> - yield() - assertCoroutineContextFromScope(coroutineScope, coroutineContext) - latch.countDown() - } - - assertTrue(inventory.leftClick(player, 36)) - latch.await() - } - } - - @EnvTest - @Nested - inner class AddItemStackWithClickHandler { - - @Test - fun `default identifier should be equals`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.DIAMOND) - var count = 0 - val condition = inventory.addItemStack(item) { _, _, _, _ -> - count++ - } - - assertNotNull(condition) - assertEquals(1, inventory.inventoryConditions.size) - assertEquals(1, inventory.itemStacks.filterNot { it.isAir }.size) - - val playerSlot = 36 - assertEquals(item, inventory.getItemStack(0)) - - assertTrue(inventory.leftClick(player, playerSlot)) - assertEquals(1, count) - - inventory.setItemStack(0, ItemStack.AIR) - inventory.addItemStack(item.withAmount(2)) - - assertTrue(inventory.leftClick(player, playerSlot)) - assertEquals(1, count) - - inventory.setItemStack(0, ItemStack.AIR) - inventory.addItemStack(ItemStack.of(Material.DIAMOND)) - assertTrue(inventory.leftClick(player, playerSlot)) - assertEquals(2, count) - } - - @Test - fun `should trigger the click handler when the item is equals`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.DIAMOND) - val slot = 0 - var count = 0 - val condition = inventory.addItemStack(item, ItemComparator.EQUALS) { _, _, _, _ -> - count++ - } - - assertNotNull(condition) - assertEquals(1, inventory.inventoryConditions.size) - assertEquals(1, inventory.itemStacks.filterNot { it.isAir }.size) - - val playerSlot = 36 - assertEquals(item, inventory.getItemStack(slot)) - assertTrue(inventory.leftClick(player, playerSlot)) - assertEquals(1, count) - - inventory.setItemStack(slot + 1, item) - assertTrue(inventory.leftClick(player, playerSlot + 1)) - assertEquals(2, count) - } - - @Test - fun `should trigger the click handler when the item is similar`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.ARROW) - var clicked = false - val condition = inventory.addItemStack(item, ItemComparator.SIMILAR) { _, _, _, _ -> - clicked = true - } - - assertNotNull(condition) - - val item2 = item.withAmount(10) - inventory.setItemStack(1, item2) - - assertTrue(inventory.leftClick(player, 37)) - assertTrue(clicked) - } - - @Test - fun `should not add item and create condition if item can't be added`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - val inventory: AbstractInventory = player.inventory - - val item = ItemStack.of(Material.ARROW) - inventory.slots.forEach { inventory.setItemStack(it, item) } - - val diamondItem = ItemStack.of(Material.DIAMOND) - val condition = inventory.addItemStack(diamondItem) { _, _, _, _ -> } - assertNull(condition) - inventory.itemStacks.forEach { assertNotEquals(diamondItem, it) } - } - } - - @EnvTest - @Nested - inner class SetCloseButton { - - @Test - fun `should set the close button`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - - val inventory = Inventory(InventoryType.CHEST_1_ROW, "Test") - val condition = inventory.setCloseButton(0) - assertNotNull(condition) - assertEquals(1, inventory.inventoryConditions.size) - assertEquals( - ItemStack.of(Material.BARRIER).withDisplayName(Component.text("❌").color(NamedTextColor.RED)), - inventory.getItemStack(0) - ) - - player.openInventory(inventory) - assertFalse(inventory.leftClick(player, 0)) - assertFalse(inventory.isViewer(player)) - assertNull(player.openInventory) - } - - @Test - fun `should set the close button who override item at the slot`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - - val inventory = Inventory(InventoryType.CHEST_1_ROW, "Test") - inventory.setItemStack(0, ItemStack.of(Material.DIAMOND)) - inventory.setCloseButton(0) - assertTrue { inventory.getItemStack(0).material() == Material.BARRIER } - - inventory.setItemStack(0, ItemStack.of(Material.DIAMOND)) - player.openInventory(inventory) - assertTrue(inventory.leftClick(player, 0)) - assertTrue(inventory.isViewer(player)) - assertEquals(inventory, player.openInventory) - } - } - - @EnvTest - @Nested - inner class SetPreviousButton { - - private fun getItem(backInventory: Inventory): ItemStack { - return getChangeItem("< ", backInventory) - } - - @Test - fun `should set the previous button`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - - val inventory = Inventory(InventoryType.CHEST_1_ROW, randomString()) - val backInventory = Inventory(InventoryType.BEACON, randomString()) - val condition = inventory.setPreviousButton(0, backInventory) - val expectedNavItem = getItem(backInventory) - assertNotNull(condition) - assertEquals(1, inventory.inventoryConditions.size) - assertEquals(expectedNavItem, inventory.getItemStack(0)) - - player.openInventory(inventory) - assertTrue(inventory.isViewer(player)) - assertEquals(inventory, player.openInventory) - assertFalse(inventory.leftClick(player, 0)) - - assertFalse(inventory.isViewer(player)) - assertTrue(backInventory.isViewer(player)) - assertEquals(backInventory, player.openInventory) - } - - @Test - fun `should set the previous button who override item at the slot`(env: Env) { - val inventory = Inventory(InventoryType.CHEST_1_ROW, randomString()) - val backInventory = Inventory(InventoryType.BEACON, randomString()) - inventory.setItemStack(0, ItemStack.of(Material.DIAMOND)) - inventory.setPreviousButton(0, backInventory) - val expectedNavItem = getItem(backInventory) - assertEquals(expectedNavItem, inventory.getItemStack(0)) - } - } - - @EnvTest - @Nested - inner class SetNextButton { - - private fun getItem(inventory: Inventory): ItemStack { - return getChangeItem("> ", inventory) - } - - @Test - fun `should set the next button`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - - val inventory = Inventory(InventoryType.CHEST_1_ROW, randomString()) - val nextInventory = Inventory(InventoryType.BLAST_FURNACE, randomString()) - val condition = inventory.setNextButton(0, nextInventory) - val expectedNavItem = getItem(nextInventory) - assertNotNull(condition) - assertEquals(1, inventory.inventoryConditions.size) - assertEquals(expectedNavItem, inventory.getItemStack(0)) - - player.openInventory(inventory) - assertTrue(inventory.isViewer(player)) - assertEquals(inventory, player.openInventory) - assertFalse(inventory.leftClick(player, 0)) - - assertFalse(inventory.isViewer(player)) - assertTrue(nextInventory.isViewer(player)) - assertEquals(nextInventory, player.openInventory) - } - - @Test - fun `should set the next button who override item at the slot`(env: Env) { - val inventory = Inventory(InventoryType.CHEST_1_ROW, randomString()) - val nextInventory = Inventory(InventoryType.ENCHANTMENT, randomString()) - inventory.setItemStack(0, ItemStack.of(Material.DIAMOND)) - inventory.setNextButton(0, nextInventory) - val expectedNavItem = getItem(nextInventory) - assertEquals(expectedNavItem, inventory.getItemStack(0)) - } - } - - @EnvTest - @Nested - inner class SetItemChangeInventory { - - private val textItem = "test" - - private fun getItem(inventory: Inventory): ItemStack { - return getChangeItem(textItem, inventory) - } - - @Test - fun `should set the change button`(env: Env) { - val instance = env.createFlatInstance() - val player = env.createPlayer(instance, randomPos()) - - val inventory = Inventory(InventoryType.CHEST_1_ROW, randomString()) - val changeInventory = Inventory(InventoryType.BLAST_FURNACE, randomString()) - val condition = inventory.setItemChangeInventory(0, changeInventory, textItem) - val expectedNavItem = getItem(changeInventory) - assertNotNull(condition) - assertEquals(1, inventory.inventoryConditions.size) - assertEquals(expectedNavItem, inventory.getItemStack(0)) - - player.openInventory(inventory) - assertTrue(inventory.isViewer(player)) - assertEquals(inventory, player.openInventory) - assertFalse(inventory.leftClick(player, 0)) - - assertFalse(inventory.isViewer(player)) - assertTrue(changeInventory.isViewer(player)) - assertEquals(changeInventory, player.openInventory) - } - - @Test - fun `should set the change button who override item at the slot`(env: Env) { - val inventory = Inventory(InventoryType.CHEST_1_ROW, randomString()) - val nextInventory = Inventory(InventoryType.ENCHANTMENT, randomString()) - inventory.setItemStack(0, ItemStack.of(Material.DIAMOND)) - inventory.setItemChangeInventory(0, nextInventory, textItem) - val expectedNavItem = getItem(nextInventory) - assertEquals(expectedNavItem, inventory.getItemStack(0)) - } - } - - private fun getChangeItem(text: String, inventory: Inventory): ItemStack { - return ItemStack.of(Material.ARROW) - .withDisplayName( - Component.text(text) - .color(NamedTextColor.GOLD) - .decoration(TextDecoration.BOLD, true) - .append( - inventory.title - .color(NamedTextColor.GRAY) - .decoration(TextDecoration.ITALIC, true) - .decoration(TextDecoration.BOLD, false) - ) - ) - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/ItemStackExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/ItemStackExtTest.kt deleted file mode 100644 index 219d671b..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/extension/ItemStackExtTest.kt +++ /dev/null @@ -1,361 +0,0 @@ -package com.github.rushyverse.api.extension - -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.format.NamedTextColor -import net.minestom.server.item.ItemStack -import net.minestom.server.item.Material -import org.junit.jupiter.api.Nested -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals - -class ItemStackExtTest { - - @Nested - inner class Builder { - - @Nested - inner class FormattedLore { - - private lateinit var builder: ItemStack.Builder - - @BeforeTest - fun onBefore() { - builder = ItemStack.builder(Material.DIAMOND_SWORD) - } - - @Test - fun `should return an empty sequence if the string is empty`() { - val expected = ItemStack.builder(Material.DIAMOND_SWORD).lore().build() - assertEquals(expected, builder.formattedLore("").build()) - } - - @Test - fun `should cut sentence without space`() { - val expected = ItemStack.builder(Material.DIAMOND_SWORD).lore( - Component.text("0123-").color(NamedTextColor.GRAY), - Component.text("4567-").color(NamedTextColor.GRAY), - Component.text("89ab-").color(NamedTextColor.GRAY), - Component.text("cdef").color(NamedTextColor.GRAY) - ).build() - assertEquals( - expected, - builder.formattedLore("0123456789abcdef", 5).build() - ) - } - - @Test - fun `should create only one component if line length is equals to the string size`() { - val sentence = "Hello World" - val expected = ItemStack.builder(Material.DIAMOND_SWORD) - .lore(Component.text(sentence).color(NamedTextColor.GRAY)) - .build() - assertEquals(expected, builder.formattedLore(sentence).build()) - } - - @Test - fun `should create multiple components by cut on the line length char adding a '-'`() { - assertEquals( - ItemStack.builder(Material.DIAMOND_SWORD) - .lore( - Component.text("Hel-").color(NamedTextColor.GRAY), - Component.text("lo").color(NamedTextColor.GRAY), - Component.text("Wor-").color(NamedTextColor.GRAY), - Component.text("ld").color(NamedTextColor.GRAY) - ) - .build(), - builder.formattedLore("Hello World", 4).build() - ) - - assertEquals( - ItemStack.builder(Material.DIAMOND_SWORD) - .lore( - Component.text("He-").color(NamedTextColor.GRAY), - Component.text("llo").color(NamedTextColor.GRAY), - Component.text("Wo-").color(NamedTextColor.GRAY), - Component.text("rld").color(NamedTextColor.GRAY) - ) - .build(), - builder.formattedLore("Hello World", 3).build() - ) - } - - @Test - fun `should create multiple element by cut on the previous space char`() { - val sentence = "Hello World" - // Indexes of "W" to "d" chars - for (i in 6..10) { - assertEquals( - ItemStack.builder(Material.DIAMOND_SWORD) - .lore( - Component.text("Hello").color(NamedTextColor.GRAY), - Component.text("World").color(NamedTextColor.GRAY) - ) - .build(), - builder.formattedLore(sentence, i).build() - ) - } - } - - @Test - fun `should create multiple components with long sentence`() { - assertEquals( - ItemStack.builder(Material.DIAMOND_SWORD) - .lore( - Component.text("This is a tool").color(NamedTextColor.GRAY), - Component.text("to create a").color(NamedTextColor.GRAY), - Component.text("game").color(NamedTextColor.GRAY) - ) - .build(), - builder.formattedLore("This is a tool to create a game", 15).build() - ) - - assertEquals( - ItemStack.builder(Material.DIAMOND_SWORD) - .lore( - Component.text("This is a tool").color(NamedTextColor.GRAY), - Component.text("to create a").color(NamedTextColor.GRAY), - Component.text("game0123456789-").color(NamedTextColor.GRAY), - Component.text("0123456789").color(NamedTextColor.GRAY) - ) - .build(), - builder.formattedLore("This is a tool to create a game01234567890123456789", 15).build() - ) - - assertEquals( - ItemStack.builder(Material.DIAMOND_SWORD) - .lore( - Component.text("Ajoutez, chattez et rejoignez").color(NamedTextColor.GRAY), - Component.text("vos amis à travers le serveur").color(NamedTextColor.GRAY) - ) - .build(), - builder.formattedLore("Ajoutez, chattez et rejoignez vos amis à travers le serveur", 30).build() - ) - - assertEquals( - ItemStack.builder(Material.DIAMOND_SWORD) - .lore( - Component.text("Ajoutez, chattez et rejoignez").color(NamedTextColor.GRAY), - Component.text("v").color(NamedTextColor.GRAY) - ) - .build(), - builder.formattedLore("Ajoutez, chattez et rejoignez v", 30).build() - ) - - assertEquals( - ItemStack.builder(Material.DIAMOND_SWORD) - .lore( - Component.text("Ajoutez, chattez et rejoignez").color(NamedTextColor.GRAY), - Component.text(" ").color(NamedTextColor.GRAY) - ) - .build(), - builder.formattedLore("Ajoutez, chattez et rejoignez ", 30).build() - ) - - assertEquals( - ItemStack.builder(Material.DIAMOND_SWORD) - .lore( - Component.text("Add, chat and join your friends through").color(NamedTextColor.GRAY), - Component.text("the server").color(NamedTextColor.GRAY) - ) - .build(), - builder.formattedLore("Add, chat and join your friends through the server", 40).build() - ) - } - - @Test - fun `should apply transformation for created components`() { - val expected = ItemStack.builder(Material.DIAMOND_SWORD) - .lore( - Component.text("Hello").color(NamedTextColor.RED), - Component.text("World").color(NamedTextColor.RED) - ) - .build() - assertEquals( - expected, - builder.formattedLore("Hello World", 5) { color(NamedTextColor.RED) }.build() - ) - } - - } - } - - @Nested - inner class FormattedLore { - - private lateinit var item: ItemStack - - @BeforeTest - fun onBefore() { - item = ItemStack.of(Material.DIAMOND_SWORD) - } - - @Test - fun `should return an empty sequence if the string is empty`() { - val expected = ItemStack.builder(Material.DIAMOND_SWORD).lore().build() - assertEquals(expected, item.withFormattedLore("")) - } - - @Test - fun `should cut sentence without space`() { - val expected = ItemStack.builder(Material.DIAMOND_SWORD).lore( - Component.text("0123-").color(NamedTextColor.GRAY), - Component.text("4567-").color(NamedTextColor.GRAY), - Component.text("89ab-").color(NamedTextColor.GRAY), - Component.text("cdef").color(NamedTextColor.GRAY) - ).build() - assertEquals( - expected, - item.withFormattedLore("0123456789abcdef", 5) - ) - } - - @Test - fun `should create only one component if line length is equals to the string size`() { - val sentence = "Hello World" - val expected = ItemStack.builder(Material.DIAMOND_SWORD) - .lore(Component.text(sentence).color(NamedTextColor.GRAY)) - .build() - assertEquals(expected, item.withFormattedLore(sentence)) - } - - @Test - fun `should create multiple components by cut on the line length char adding a '-'`() { - assertEquals( - ItemStack.builder(Material.DIAMOND_SWORD) - .lore( - Component.text("Hel-").color(NamedTextColor.GRAY), - Component.text("lo").color(NamedTextColor.GRAY), - Component.text("Wor-").color(NamedTextColor.GRAY), - Component.text("ld").color(NamedTextColor.GRAY) - ) - .build(), - item.withFormattedLore("Hello World", 4) - ) - - assertEquals( - ItemStack.builder(Material.DIAMOND_SWORD) - .lore( - Component.text("He-").color(NamedTextColor.GRAY), - Component.text("llo").color(NamedTextColor.GRAY), - Component.text("Wo-").color(NamedTextColor.GRAY), - Component.text("rld").color(NamedTextColor.GRAY) - ) - .build(), - item.withFormattedLore("Hello World", 3) - ) - } - - @Test - fun `should create multiple element by cut on the previous space char`() { - val sentence = "Hello World" - // Indexes of "W" to "d" chars - for (i in 6..10) { - assertEquals( - ItemStack.builder(Material.DIAMOND_SWORD) - .lore( - Component.text("Hello").color(NamedTextColor.GRAY), - Component.text("World").color(NamedTextColor.GRAY) - ) - .build(), - item.withFormattedLore(sentence, i) - ) - } - } - - @Test - fun `should create multiple components with long sentence`() { - assertEquals( - ItemStack.builder(Material.DIAMOND_SWORD) - .lore( - Component.text("This is a tool").color(NamedTextColor.GRAY), - Component.text("to create a").color(NamedTextColor.GRAY), - Component.text("game").color(NamedTextColor.GRAY) - ) - .build(), - item.withFormattedLore("This is a tool to create a game", 15) - ) - - assertEquals( - ItemStack.builder(Material.DIAMOND_SWORD) - .lore( - Component.text("This is a tool").color(NamedTextColor.GRAY), - Component.text("to create a").color(NamedTextColor.GRAY), - Component.text("game0123456789-").color(NamedTextColor.GRAY), - Component.text("0123456789").color(NamedTextColor.GRAY) - ) - .build(), - item.withFormattedLore("This is a tool to create a game01234567890123456789", 15) - ) - - assertEquals( - ItemStack.builder(Material.DIAMOND_SWORD) - .lore( - Component.text("Ajoutez, chattez et rejoignez").color(NamedTextColor.GRAY), - Component.text("vos amis à travers le serveur").color(NamedTextColor.GRAY) - ) - .build(), - item.withFormattedLore("Ajoutez, chattez et rejoignez vos amis à travers le serveur", 30) - ) - - assertEquals( - ItemStack.builder(Material.DIAMOND_SWORD) - .lore( - Component.text("Ajoutez, chattez et rejoignez").color(NamedTextColor.GRAY), - Component.text("v").color(NamedTextColor.GRAY) - ) - .build(), - item.withFormattedLore("Ajoutez, chattez et rejoignez v", 30) - ) - - assertEquals( - ItemStack.builder(Material.DIAMOND_SWORD) - .lore( - Component.text("Ajoutez, chattez et rejoignez").color(NamedTextColor.GRAY), - Component.text(" ").color(NamedTextColor.GRAY) - ) - .build(), - item.withFormattedLore("Ajoutez, chattez et rejoignez ", 30) - ) - - assertEquals( - ItemStack.builder(Material.DIAMOND_SWORD) - .lore( - Component.text("Add, chat and join your friends through").color(NamedTextColor.GRAY), - Component.text("the server").color(NamedTextColor.GRAY) - ) - .build(), - item.withFormattedLore("Add, chat and join your friends through the server", 40) - ) - } - - @Test - fun `should apply transformation for created components`() { - val expected = ItemStack.builder(Material.DIAMOND_SWORD) - .lore( - Component.text("Hello").color(NamedTextColor.RED), - Component.text("World").color(NamedTextColor.RED) - ) - .build() - assertEquals( - expected, - item.withFormattedLore("Hello World", 5) { color(NamedTextColor.RED) } - ) - } - - } - - @Nested - inner class WithLore { - - @Test - fun `should set a single lore`() { - val item = ItemStack.of(Material.STONE).withLore(Component.text("Hello")) - assertEquals( - ItemStack.builder(Material.STONE).lore(Component.text("Hello")).build(), - item - ) - } - - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/ListenerExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/ListenerExtTest.kt deleted file mode 100644 index a9602c51..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/extension/ListenerExtTest.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.github.rushyverse.api.extension - -import com.github.rushyverse.api.utils.assertCoroutineContextFromScope -import io.mockk.every -import io.mockk.mockk -import io.mockk.slot -import io.mockk.spyk -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.yield -import net.minestom.server.event.EventListener -import net.minestom.server.event.EventNode -import net.minestom.server.event.player.PlayerSkinInitEvent -import java.util.concurrent.CountDownLatch -import kotlin.coroutines.coroutineContext -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotEquals -import kotlin.test.assertTrue - -class ListenerExtTest { - - @Test - fun `should handle event in coroutine context by adding listener suspend`() { - val node = spyk(EventNode.all("test")) - val slot = slot>() - every { node.addListener(capture(slot)) } returns mockk() - - val currentThread = Thread.currentThread() - val latch = CountDownLatch(1) - val event = mockk() - - var executed = false - val scope = CoroutineScope(Dispatchers.Default) - node.addListenerSuspend(scope) { - assertCoroutineContextFromScope(scope, coroutineContext) - assertEquals(event, it) - executed = true - - assertEquals(currentThread, Thread.currentThread()) - yield() - assertNotEquals(currentThread, Thread.currentThread()) - latch.countDown() - } - - slot.captured.run(event) - latch.await() - assertTrue(executed) - - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/MathExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/MathExtTest.kt deleted file mode 100644 index 06f9aa3b..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/extension/MathExtTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.github.rushyverse.api.extension - -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Nested -import kotlin.test.Test -import kotlin.test.assertEquals - -class MathExtTest { - - @Nested - @DisplayName("Get min and max") - inner class MinMaxOf { - - @Test - fun `a is inferior to b`() { - val expectedA = 1 - val expectedB = 2 - val (a, b) = minMaxOf(expectedA, expectedB) - assertEquals(expectedA, a) - assertEquals(expectedB, b) - - val (a2, b2) = minMaxOf(expectedB, expectedA) - assertEquals(expectedA, a2) - assertEquals(expectedB, b2) - } - - @Test - fun `a is equals to b`() { - val expectedA = 2 - val expectedB = 2 - val (a, b) = minMaxOf(expectedA, expectedB) - assertEquals(expectedA, a) - assertEquals(expectedB, b) - - val (a2, b2) = minMaxOf(expectedB, expectedA) - assertEquals(expectedA, a2) - assertEquals(expectedB, b2) - } - - @Test - fun `a is superior to b`() { - val expectedA = 3 - val expectedB = 2 - val (b, a) = minMaxOf(expectedA, expectedB) - assertEquals(expectedA, a) - assertEquals(expectedB, b) - - val (b2, a2) = minMaxOf(expectedB, expectedA) - assertEquals(expectedA, a2) - assertEquals(expectedB, b2) - } - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/NumberExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/NumberExtTest.kt deleted file mode 100644 index 2a6a89b2..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/extension/NumberExtTest.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.github.rushyverse.api.extension - -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource -import kotlin.test.Test -import kotlin.test.assertEquals - -class NumberExtTest { - - @Test - fun `roman numerals values associate all numbers to their roman numerals`() { - val expected = mapOf( - 1000 to "M", - 900 to "CM", - 500 to "D", - 400 to "CD", - 100 to "C", - 90 to "XC", - 50 to "L", - 40 to "XL", - 10 to "X", - 9 to "IX", - 5 to "V", - 4 to "IV", - 1 to "I" - ) - assertEquals(expected, ROMAN_NUMERALS_VALUES) - } - - @Test - fun `roman numerals array should be ordered from the largest to the smallest`() { - val expected = listOf("M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I") - assertEquals(expected, ROMAN_NUMERALS.toList()) - } - - @Test - fun `roman values array should be ordered from the largest to the smallest`() { - val expected = listOf(1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1) - assertEquals(expected, ROMAN_VALUES.toList()) - } - - @Nested - inner class IntTest { - - @Nested - inner class RomanNumerals { - - @ParameterizedTest - @ValueSource(ints = [0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10]) - fun `should throw exception when number is negative or zero`(number: Int) { - val ex = assertThrows { number.toRomanNumerals() } - assertEquals("Number must be positive", ex.message) - } - - @ParameterizedTest - @ValueSource(ints = [4000, 4001, 4002, 4003, 4004, 4005, 4006, 4007, 4008, 4009, 4010]) - fun `should throw exception when number is greater than 3999`(number: Int) { - val ex = assertThrows { number.toRomanNumerals() } - assertEquals("Number must be less than 4000", ex.message) - } - - @ParameterizedTest - @ValueSource(ints = [1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000]) - fun `should return pure roman numerals`(number: Int) { - val expected = when (number) { - 1 -> "I" - 4 -> "IV" - 5 -> "V" - 9 -> "IX" - 10 -> "X" - 40 -> "XL" - 50 -> "L" - 90 -> "XC" - 100 -> "C" - 400 -> "CD" - 500 -> "D" - 900 -> "CM" - 1000 -> "M" - else -> throw IllegalArgumentException("Invalid number") - } - assertEquals(expected, number.toRomanNumerals()) - } - - @Test - fun `should return complex roman numerals`() { - NumberExtTest::class.java.getResourceAsStream("/cases/roman/numerals.txt")!!.bufferedReader().useLines { lines -> - lines.forEach { line -> - val (number, expected) = line.split(" ") - assertEquals(expected, number.toInt().toRomanNumerals()) - } - } - } - } - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/PosExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/PosExtTest.kt deleted file mode 100644 index d3ec841d..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/extension/PosExtTest.kt +++ /dev/null @@ -1,143 +0,0 @@ -package com.github.rushyverse.api.extension - -import net.minestom.server.coordinate.Pos -import org.junit.jupiter.api.Nested -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class PosExtTest { - - @Nested - inner class CenterRelative { - - @Test - fun `should return the center relative to the other position with positive`() { - val pos = Pos(1.0, 2.0, 3.0) - val other = Pos(2.0, 3.0, 4.0) - val expected = Pos(1.5, 2.5, 3.5) - val actual = pos.centerRelative(other) - assertEquals(expected, actual) - } - - @Test - fun `should return the center relative to the other position with negative`() { - val pos = Pos(-1.0, -2.0, -3.0) - val other = Pos(-2.0, -3.0, -4.0) - val expected = Pos(-1.5, -2.5, -3.5) - val actual = pos.centerRelative(other) - assertEquals(expected, actual) - } - - @Test - fun `should return the center relative to the other position with mixed`() { - val pos = Pos(1.0, -2.0, 3.0) - val other = Pos(-2.0, 3.0, -4.0) - val expected = Pos(-0.5, 0.5, -0.5) - val actual = pos.centerRelative(other) - assertEquals(expected, actual) - } - } - - @Nested - inner class IsInCube { - - @Test - fun `should return true if the position is in the cube`() { - val min = Pos(0.0, 0.0, 0.0) - val max = Pos(10.0, 10.0, 10.0) - for (x in 0..10) { - for (y in 0..10) { - for (z in 0..10) { - val pos = Pos(x.toDouble(), y.toDouble(), z.toDouble()) - assertTrue { pos.isInCube(min, max) } - } - } - } - } - - @Test - fun `should return false if the position is not in the cube`() { - val min = Pos(0.0, 0.0, 0.0) - val max = Pos(10.0, 10.0, 10.0) - assertFalse { Pos(-0.1, 0.0, 0.0).isInCube(min, max) } - assertFalse { Pos(10.1, 0.0, 0.0).isInCube(min, max) } - assertFalse { Pos(0.0, -0.1, 0.0).isInCube(min, max) } - assertFalse { Pos(0.0, 10.1, 0.0).isInCube(min, max) } - assertFalse { Pos(0.0, 0.0, -0.1).isInCube(min, max) } - assertFalse { Pos(0.0, 0.0, 10.1).isInCube(min, max) } - - for (x in -10..-1) { - for (y in -10..-1) { - for (z in -10..-1) { - val pos = Pos(x.toDouble(), y.toDouble(), z.toDouble()) - assertFalse { pos.isInCube(min, max) } - } - } - } - - for (x in 11..20) { - for (y in 11..20) { - for (z in 11..20) { - val pos = Pos(x.toDouble(), y.toDouble(), z.toDouble()) - assertFalse { pos.isInCube(min, max) } - } - } - } - } - } - - @Nested - inner class IsInCylinder { - - @Test - fun `should return true if the position is in the cylinder`() { - val positionCylinder = Pos(0.0, 0.0, 0.0) - val radius = 10.0 - val limitY = 0.0..10.0 - for (x in -10..10) { - for (y in 0..10) { - val posX = Pos(x.toDouble(), y.toDouble(), 0.0) - assertTrue { posX.isInCylinder(positionCylinder, radius, limitY) } - - val posZ = Pos(0.0, y.toDouble(), x.toDouble()) - assertTrue { posZ.isInCylinder(positionCylinder, radius, limitY) } - } - } - } - - @Test - fun `should return false if the position is not in the cylinder`() { - val positionCylinder = Pos(0.0, 0.0, 0.0) - val radius = 10.0 - val limitY = 0.0..10.0 - assertFalse { Pos(-10.1, 0.0, 0.0).isInCylinder(positionCylinder, radius, limitY) } - assertFalse { Pos(10.1, 0.0, 0.0).isInCylinder(positionCylinder, radius, limitY) } - assertFalse { Pos(0.0, -0.1, 0.0).isInCylinder(positionCylinder, radius, limitY) } - assertFalse { Pos(0.0, 10.1, 0.0).isInCylinder(positionCylinder, radius, limitY) } - assertFalse { Pos(0.0, 0.0, -10.1).isInCylinder(positionCylinder, radius, limitY) } - assertFalse { Pos(0.0, 0.0, 10.1).isInCylinder(positionCylinder, radius, limitY) } - - for (x in -20..-11) { - for (y in -10..-1) { - val posX = Pos(x.toDouble(), y.toDouble(), 0.0) - assertFalse { posX.isInCylinder(positionCylinder, radius, limitY) } - - val posZ = Pos(0.0, y.toDouble(), x.toDouble()) - assertFalse { posZ.isInCylinder(positionCylinder, radius, limitY) } - } - } - - for (x in 11..20) { - for (y in 11..20) { - val posX = Pos(x.toDouble(), y.toDouble(), 0.0) - assertFalse { posX.isInCylinder(positionCylinder, radius, limitY) } - - val posZ = Pos(0.0, y.toDouble(), x.toDouble()) - assertFalse { posZ.isInCylinder(positionCylinder, radius, limitY) } - } - } - } - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/PropertyExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/PropertyExtTest.kt deleted file mode 100644 index c2767f5a..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/extension/PropertyExtTest.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.github.rushyverse.api.extension - -import com.github.rushyverse.api.utils.randomString -import net.minestom.server.network.packet.server.play.PlayerInfoPacket -import net.minestom.server.network.player.GameProfile -import kotlin.test.Test -import kotlin.test.assertEquals - -class PropertyExtTest { - - @Test - fun `should create a GameProfile property for textures`() { - val textures = randomString() - val signature = randomString() - val expected = GameProfile.Property("textures", textures, signature) - val actual = GameProfileTextureProperty(textures, signature) - assertEquals(expected, actual) - } - - @Test - fun `should create an AddPlayer property for textures`() { - val textures = randomString() - val signature = randomString() - val expected = PlayerInfoPacket.AddPlayer.Property("textures", textures, signature) - val actual = AddPlayerTextureProperty(textures, signature) - assertEquals(expected, actual) - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt deleted file mode 100644 index 16ee7ff7..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt +++ /dev/null @@ -1,341 +0,0 @@ -package com.github.rushyverse.api.extension - -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.TextComponent -import net.kyori.adventure.text.event.ClickEvent -import net.kyori.adventure.text.format.NamedTextColor -import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder -import org.junit.jupiter.api.Nested -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull - -class StringExtTest { - - @Test - fun `default lore line length`() { - assertEquals(30, DEFAULT_LORE_LINE_LENGTH) - } - - @Nested - inner class SequenceToLore { - - @Test - fun `should return empty component if sequence is empty`() { - assertEquals(emptyList(), emptySequence().toLore()) - } - - @Test - fun `should set color gray by default`() { - val components = sequenceOf("a", "b", "c").toLore() - assertEquals( - listOf( - Component.text().content("a").color(NamedTextColor.GRAY).build(), - Component.text().content("b").color(NamedTextColor.GRAY).build(), - Component.text().content("c").color(NamedTextColor.GRAY).build() - ), - components - ) - } - - @Test - fun `should return component with all strings`() { - val components = sequenceOf("Hello", "World").toLore() {} - assertEquals( - listOf( - Component.text().content("Hello").build(), - Component.text().content("World").build() - ), - components - ) - } - - @Test - fun `should return component with all strings and transform`() { - val components = sequenceOf("Hello", "World").toLore { color(NamedTextColor.RED) } - assertEquals( - listOf( - Component.text().content("Hello").color(NamedTextColor.RED).build(), - Component.text().content("World").color(NamedTextColor.RED).build() - ), - components - ) - } - - } - - @Nested - inner class CollectionToLore { - - @Test - fun `should return empty component if sequence is empty`() { - assertEquals(emptyList(), emptyList().toLore()) - } - - @Test - fun `should set color gray by default`() { - val components = listOf("a", "b", "c").toLore() - assertEquals( - listOf( - Component.text().content("a").color(NamedTextColor.GRAY).build(), - Component.text().content("b").color(NamedTextColor.GRAY).build(), - Component.text().content("c").color(NamedTextColor.GRAY).build() - ), - components - ) - } - - @Test - fun `should return component with all strings`() { - val components = listOf("Hello", "World").toLore() {} - assertEquals( - listOf( - Component.text().content("Hello").build(), - Component.text().content("World").build() - ), - components - ) - } - - @Test - fun `should return component with all strings and transform`() { - val components = listOf("Hello", "World").toLore { color(NamedTextColor.RED) } - assertEquals( - listOf( - Component.text().content("Hello").color(NamedTextColor.RED).build(), - Component.text().content("World").color(NamedTextColor.RED).build() - ), - components - ) - } - - } - - @Nested - inner class ToFormattedLore { - - @Test - fun `should return an empty sequence if the string is empty`() { - assertEquals(emptyList(), "".toFormattedLore(10)) - } - - @Test - fun `should cut sentence without space`() { - val sentence = "0123456789abcdef" - assertEquals( - listOf("0123-", "4567-", "89ab-", "cdef"), - sentence.toFormattedLore(5) - ) - } - - @Test - fun `should create only one element if line length is greater than string size`() { - for (i in 1..100) { - val string = "a".repeat(i) - val sequence = string.toFormattedLore(i + 1) - assert(sequence.count() == 1) - } - } - - @Test - fun `should create only one element if line length is equals to the string size`() { - val sentence = "Hello World" - assertEquals(listOf(sentence), sentence.toFormattedLore(sentence.length)) - } - - @Test - fun `should create multiple elements by cut on the space char`() { - val sequence = "Hello World".toFormattedLore(5) - assertEquals(listOf("Hello", "World"), sequence) - } - - @Test - fun `should create multiple elements by cut on the line length char adding a '-'`() { - val sequence1 = "Hello World".toFormattedLore(4) - assertEquals(listOf("Hel-", "lo", "Wor-", "ld"), sequence1) - - val sequence2 = "Hello World".toFormattedLore(3) - assertEquals(listOf("He-", "llo", "Wo-", "rld"), sequence2) - } - - @Test - fun `should create multiple element by cut on the previous space char`() { - // Indexes of "W" to "d" chars - for (i in 6..10) { - assertEquals(listOf("Hello", "World"), "Hello World".toFormattedLore(i)) - } - } - - @Test - fun `should create multiple element with long sentence`() { - assertEquals( - listOf("This is a tool", "to create a", "game"), - "This is a tool to create a game".toFormattedLore(15) - ) - - assertEquals( - listOf("This is a tool", "to create a", "game0123456789-", "0123456789"), - "This is a tool to create a game01234567890123456789".toFormattedLore(15) - ) - - assertEquals( - listOf("Ajoutez, chattez et rejoignez", "vos amis à travers le serveur"), - "Ajoutez, chattez et rejoignez vos amis à travers le serveur".toFormattedLore(30), - ) - - assertEquals( - listOf("Ajoutez, chattez et rejoignez", "v"), - "Ajoutez, chattez et rejoignez v".toFormattedLore(30), - ) - - assertEquals( - listOf("Ajoutez, chattez et rejoignez", " "), - "Ajoutez, chattez et rejoignez ".toFormattedLore(30), - ) - - assertEquals( - listOf("Add, chat and join your friends through", "the server"), - "Add, chat and join your friends through the server".toFormattedLore(40), - ) - } - } - - @Nested - inner class ToFormattedLoreSequence { - - @Test - fun `should return an empty sequence if the string is empty`() { - assertEquals(emptySequence(), "".toFormattedLoreSequence(10)) - } - - @Test - fun `should cut sentence without space`() { - val sentence = "0123456789abcdef" - assertEquals( - listOf("0123-", "4567-", "89ab-", "cdef"), - sentence.toFormattedLoreSequence(5).toList() - ) - } - - @Test - fun `should create only one element if line length is greater than string size`() { - for (i in 1..100) { - val string = "a".repeat(i) - val sequence = string.toFormattedLoreSequence(i + 1) - assert(sequence.count() == 1) - } - } - - @Test - fun `should create only one element if line length is equals to the string size`() { - val sentence = "Hello World" - assertEquals(listOf(sentence), sentence.toFormattedLoreSequence(sentence.length).toList()) - } - - @Test - fun `should create multiple elements by cut on the space char`() { - val sequence = "Hello World".toFormattedLoreSequence(5).toList() - assertEquals(listOf("Hello", "World"), sequence) - } - - @Test - fun `should create multiple elements by cut on the line length char adding a '-'`() { - val sequence1 = "Hello World".toFormattedLoreSequence(4).toList() - assertEquals(listOf("Hel-", "lo", "Wor-", "ld"), sequence1) - - val sequence2 = "Hello World".toFormattedLoreSequence(3).toList() - assertEquals(listOf("He-", "llo", "Wo-", "rld"), sequence2) - } - - @Test - fun `should create multiple element by cut on the previous space char`() { - // Indexes of "W" to "d" chars - for (i in 6..10) { - assertEquals(listOf("Hello", "World"), "Hello World".toFormattedLoreSequence(i).toList()) - } - } - - @Test - fun `should create multiple element with long sentence`() { - assertEquals( - listOf("This is a tool", "to create a", "game"), - "This is a tool to create a game".toFormattedLoreSequence(15).toList() - ) - - assertEquals( - listOf("This is a tool", "to create a", "game0123456789-", "0123456789"), - "This is a tool to create a game01234567890123456789".toFormattedLoreSequence(15).toList() - ) - - assertEquals( - listOf("Ajoutez, chattez et rejoignez", "vos amis à travers le serveur"), - "Ajoutez, chattez et rejoignez vos amis à travers le serveur".toFormattedLoreSequence(30).toList(), - ) - - assertEquals( - listOf("Ajoutez, chattez et rejoignez", "v"), - "Ajoutez, chattez et rejoignez v".toFormattedLoreSequence(30).toList(), - ) - - assertEquals( - listOf("Ajoutez, chattez et rejoignez", " "), - "Ajoutez, chattez et rejoignez ".toFormattedLoreSequence(30).toList(), - ) - - assertEquals( - listOf("Add, chat and join your friends through", "the server"), - "Add, chat and join your friends through the server".toFormattedLoreSequence(40).toList(), - ) - } - } - - @Nested - inner class AsMiniComponent { - - @Test - fun `should return simple component text with simple string`() { - val string = "Hello World!" - val component = string.asMiniComponent() - component as TextComponent - assertEquals("Hello World!", component.content()) - } - - @Test - fun `should return component text with color`() { - val string = "Hello World!" - val component = string.asMiniComponent() - component as TextComponent - - assertEquals("Hello World!", component.content()) - - val color = component.color() - assertNotNull(color) - assertEquals("#ff5555", color.asHexString()) - } - - @Test - fun `should return component with click event`() { - val string = "Click me!" - val component = string.asMiniComponent() - component as TextComponent - - assertEquals("Click me!", component.content()) - - val clickEvent = component.clickEvent() - assertNotNull(clickEvent) - assertEquals(ClickEvent.Action.RUN_COMMAND, clickEvent.action()) - assertEquals("/test", clickEvent.value()) - } - - @Test - fun `should return component with custom tag`() { - val string = "Hello !" - val component = string.asMiniComponent( - Placeholder.unparsed("test", "my test") - ) - component as TextComponent - assertEquals("Hello my test!", component.content()) - } - - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/image/MapImageMathTest.kt b/src/test/kotlin/com/github/rushyverse/api/image/MapImageMathTest.kt deleted file mode 100644 index abd40b28..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/image/MapImageMathTest.kt +++ /dev/null @@ -1,806 +0,0 @@ -package com.github.rushyverse.api.image - -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals - -class MapImageMathTest { - - @Nested - inner class Up { - - private lateinit var instance: MapImageMath - - @BeforeTest - fun onBefore() { - instance = MapImageMath.Up - } - - @Test - fun `should have the correct yaw`() { - assertEquals(0f, instance.yaw) - } - - @Test - fun `should have the correct pitch`() { - assertEquals(270f, instance.pitch) - } - - @Nested - inner class ComputeX { - - @Test - fun `should always return 0 if no element per line`() { - repeat(10) { - assertThrows { - assertEquals(0, instance.computeX(0, it, itemFramesPerLine = 0)) - } - } - } - - @Test - fun `should compute the correct x with begin equals to 0`() { - val begin = 0 - val itemFramesPerLine = 3 - - assertEquals(0, instance.computeX(begin, 0, itemFramesPerLine)) - assertEquals(1, instance.computeX(begin, 1, itemFramesPerLine)) - assertEquals(2, instance.computeX(begin, 2, itemFramesPerLine)) - - assertEquals(0, instance.computeX(begin, 3, itemFramesPerLine)) - assertEquals(1, instance.computeX(begin, 4, itemFramesPerLine)) - assertEquals(2, instance.computeX(begin, 5, itemFramesPerLine)) - } - - @Test - fun `should compute the correct x with begin greater than 0`() { - val begin = 5 - val itemFramesPerLine = 3 - - assertEquals(5, instance.computeX(begin, 0, itemFramesPerLine)) - assertEquals(6, instance.computeX(begin, 1, itemFramesPerLine)) - assertEquals(7, instance.computeX(begin, 2, itemFramesPerLine)) - - assertEquals(5, instance.computeX(begin, 3, itemFramesPerLine)) - assertEquals(6, instance.computeX(begin, 4, itemFramesPerLine)) - assertEquals(7, instance.computeX(begin, 5, itemFramesPerLine)) - } - - } - - @Nested - inner class ComputeY { - - @Test - fun `should always return 0 if no element per line`() { - repeat(10) { - assertEquals(0, instance.computeY(0, it, itemFramesPerLine = 0)) - } - } - - @ParameterizedTest - @ValueSource(ints = [-1, 0, 1]) - fun `should stay on the same y`(beginY: Int) { - repeat(10) { - assertEquals(0, instance.computeY(0, it, 5)) - } - } - - } - - @Nested - inner class ComputeZ { - - @Test - fun `should throws exception with no element per line`() { - repeat(10) { - assertThrows { - assertEquals(0, instance.computeZ(0, it, itemFramesPerLine = 0)) - } - } - } - - @Test - fun `should compute the correct z with begin equals to 0`() { - val begin = 0 - val itemFramesPerLine = 3 - - assertEquals(0, instance.computeZ(begin, 0, itemFramesPerLine)) - assertEquals(0, instance.computeZ(begin, 1, itemFramesPerLine)) - assertEquals(0, instance.computeZ(begin, 2, itemFramesPerLine)) - - assertEquals(1, instance.computeZ(begin, 3, itemFramesPerLine)) - assertEquals(1, instance.computeZ(begin, 4, itemFramesPerLine)) - assertEquals(1, instance.computeZ(begin, 5, itemFramesPerLine)) - - assertEquals(2, instance.computeZ(begin, 6, itemFramesPerLine)) - assertEquals(2, instance.computeZ(begin, 7, itemFramesPerLine)) - assertEquals(2, instance.computeZ(begin, 8, itemFramesPerLine)) - } - - @Test - fun `should compute the correct z with begin greater than 0`() { - val begin = 5 - val itemFramesPerLine = 3 - - assertEquals(5, instance.computeZ(begin, 0, itemFramesPerLine)) - assertEquals(5, instance.computeZ(begin, 1, itemFramesPerLine)) - assertEquals(5, instance.computeZ(begin, 2, itemFramesPerLine)) - - assertEquals(6, instance.computeZ(begin, 3, itemFramesPerLine)) - assertEquals(6, instance.computeZ(begin, 4, itemFramesPerLine)) - assertEquals(6, instance.computeZ(begin, 5, itemFramesPerLine)) - - assertEquals(7, instance.computeZ(begin, 6, itemFramesPerLine)) - assertEquals(7, instance.computeZ(begin, 7, itemFramesPerLine)) - assertEquals(7, instance.computeZ(begin, 8, itemFramesPerLine)) - } - - } - } - - @Nested - inner class Down { - - private lateinit var instance: MapImageMath - - @BeforeTest - fun onBefore() { - instance = MapImageMath.Down - } - - @Test - fun `should have the correct yaw`() { - assertEquals(0f, instance.yaw) - } - - @Test - fun `should have the correct pitch`() { - assertEquals(90f, instance.pitch) - } - - @Nested - inner class ComputeX { - - @Test - fun `should always return 0 if no element per line`() { - repeat(10) { - assertThrows { - assertEquals(0, instance.computeX(0, it, itemFramesPerLine = 0)) - } - } - } - - @Test - fun `should compute the correct x with begin equals to 0`() { - val begin = 0 - val itemFramesPerLine = 3 - - assertEquals(0, instance.computeX(begin, 0, itemFramesPerLine)) - assertEquals(1, instance.computeX(begin, 1, itemFramesPerLine)) - assertEquals(2, instance.computeX(begin, 2, itemFramesPerLine)) - - assertEquals(0, instance.computeX(begin, 3, itemFramesPerLine)) - assertEquals(1, instance.computeX(begin, 4, itemFramesPerLine)) - assertEquals(2, instance.computeX(begin, 5, itemFramesPerLine)) - } - - @Test - fun `should compute the correct x with begin greater than 0`() { - val begin = 5 - val itemFramesPerLine = 3 - - assertEquals(5, instance.computeX(begin, 0, itemFramesPerLine)) - assertEquals(6, instance.computeX(begin, 1, itemFramesPerLine)) - assertEquals(7, instance.computeX(begin, 2, itemFramesPerLine)) - - assertEquals(5, instance.computeX(begin, 3, itemFramesPerLine)) - assertEquals(6, instance.computeX(begin, 4, itemFramesPerLine)) - assertEquals(7, instance.computeX(begin, 5, itemFramesPerLine)) - } - - } - - @Nested - inner class ComputeY { - - @Test - fun `should always return 0 if no element per line`() { - repeat(10) { - assertEquals(0, instance.computeY(0, it, itemFramesPerLine = 0)) - } - } - - @ParameterizedTest - @ValueSource(ints = [-1, 0, 1]) - fun `should stay on the same y`(beginY: Int) { - repeat(10) { - assertEquals(0, instance.computeY(0, it, 5)) - } - } - - } - - @Nested - inner class ComputeZ { - - @Test - fun `should throws exception with no element per line`() { - repeat(10) { - assertThrows { - assertEquals(0, instance.computeZ(0, it, itemFramesPerLine = 0)) - } - } - } - - @Test - fun `should compute the correct z with begin equals to 0`() { - val begin = 0 - val itemFramesPerLine = 3 - - assertEquals(0, instance.computeZ(begin, 0, itemFramesPerLine)) - assertEquals(0, instance.computeZ(begin, 1, itemFramesPerLine)) - assertEquals(0, instance.computeZ(begin, 2, itemFramesPerLine)) - - assertEquals(-1, instance.computeZ(begin, 3, itemFramesPerLine)) - assertEquals(-1, instance.computeZ(begin, 4, itemFramesPerLine)) - assertEquals(-1, instance.computeZ(begin, 5, itemFramesPerLine)) - - assertEquals(-2, instance.computeZ(begin, 6, itemFramesPerLine)) - assertEquals(-2, instance.computeZ(begin, 7, itemFramesPerLine)) - assertEquals(-2, instance.computeZ(begin, 8, itemFramesPerLine)) - } - - @Test - fun `should compute the correct z with begin greater than 0`() { - val begin = 5 - val itemFramesPerLine = 3 - - assertEquals(5, instance.computeZ(begin, 0, itemFramesPerLine)) - assertEquals(5, instance.computeZ(begin, 1, itemFramesPerLine)) - assertEquals(5, instance.computeZ(begin, 2, itemFramesPerLine)) - - assertEquals(4, instance.computeZ(begin, 3, itemFramesPerLine)) - assertEquals(4, instance.computeZ(begin, 4, itemFramesPerLine)) - assertEquals(4, instance.computeZ(begin, 5, itemFramesPerLine)) - - assertEquals(3, instance.computeZ(begin, 6, itemFramesPerLine)) - assertEquals(3, instance.computeZ(begin, 7, itemFramesPerLine)) - assertEquals(3, instance.computeZ(begin, 8, itemFramesPerLine)) - } - - } - - } - - @Nested - inner class North { - - private lateinit var instance: MapImageMath - - @BeforeTest - fun onBefore() { - instance = MapImageMath.North - } - - @Test - fun `should have the correct yaw`() { - assertEquals(180f, instance.yaw) - } - - @Test - fun `should have the correct pitch`() { - assertEquals(0f, instance.pitch) - } - - @Nested - inner class ComputeX { - - @Test - fun `should always return 0 if no element per line`() { - repeat(10) { - assertThrows { - assertEquals(0, instance.computeX(0, it, itemFramesPerLine = 0)) - } - } - } - - @Test - fun `should compute the correct x with begin equals to 0`() { - val begin = 0 - val itemFramesPerLine = 3 - - assertEquals(0, instance.computeX(begin, 0, itemFramesPerLine)) - assertEquals(-1, instance.computeX(begin, 1, itemFramesPerLine)) - assertEquals(-2, instance.computeX(begin, 2, itemFramesPerLine)) - - assertEquals(0, instance.computeX(begin, 3, itemFramesPerLine)) - assertEquals(-1, instance.computeX(begin, 4, itemFramesPerLine)) - assertEquals(-2, instance.computeX(begin, 5, itemFramesPerLine)) - } - - @Test - fun `should compute the correct x with begin greater than 0`() { - val begin = 5 - val itemFramesPerLine = 3 - - assertEquals(5, instance.computeX(begin, 0, itemFramesPerLine)) - assertEquals(4, instance.computeX(begin, 1, itemFramesPerLine)) - assertEquals(3, instance.computeX(begin, 2, itemFramesPerLine)) - - assertEquals(5, instance.computeX(begin, 3, itemFramesPerLine)) - assertEquals(4, instance.computeX(begin, 4, itemFramesPerLine)) - assertEquals(3, instance.computeX(begin, 5, itemFramesPerLine)) - } - - } - - @Nested - inner class ComputeY { - - @Test - fun `should throws exception with no element per line`() { - repeat(10) { - assertThrows { - assertEquals(0, instance.computeY(0, it, itemFramesPerLine = 0)) - } - } - } - - @Test - fun `should compute the correct y with begin equals to 0`() { - val begin = 0 - val itemFramesPerLine = 3 - - assertEquals(0, instance.computeY(begin, 0, itemFramesPerLine)) - assertEquals(0, instance.computeY(begin, 1, itemFramesPerLine)) - assertEquals(0, instance.computeY(begin, 2, itemFramesPerLine)) - - assertEquals(-1, instance.computeY(begin, 3, itemFramesPerLine)) - assertEquals(-1, instance.computeY(begin, 4, itemFramesPerLine)) - assertEquals(-1, instance.computeY(begin, 5, itemFramesPerLine)) - - assertEquals(-2, instance.computeY(begin, 6, itemFramesPerLine)) - assertEquals(-2, instance.computeY(begin, 7, itemFramesPerLine)) - assertEquals(-2, instance.computeY(begin, 8, itemFramesPerLine)) - } - - @Test - fun `should compute the correct y with begin greater than 0`() { - val begin = 5 - val itemFramesPerLine = 3 - - assertEquals(5, instance.computeY(begin, 0, itemFramesPerLine)) - assertEquals(5, instance.computeY(begin, 1, itemFramesPerLine)) - assertEquals(5, instance.computeY(begin, 2, itemFramesPerLine)) - - assertEquals(4, instance.computeY(begin, 3, itemFramesPerLine)) - assertEquals(4, instance.computeY(begin, 4, itemFramesPerLine)) - assertEquals(4, instance.computeY(begin, 5, itemFramesPerLine)) - - assertEquals(3, instance.computeY(begin, 6, itemFramesPerLine)) - assertEquals(3, instance.computeY(begin, 7, itemFramesPerLine)) - assertEquals(3, instance.computeY(begin, 8, itemFramesPerLine)) - } - } - - @Nested - inner class ComputeZ { - - @Test - fun `should always return 0 if no element per line`() { - repeat(10) { - assertEquals(0, instance.computeZ(0, it, itemFramesPerLine = 0)) - } - } - - @ParameterizedTest - @ValueSource(ints = [-1, 0, 1]) - fun `should stay on the same z`(beginZ: Int) { - repeat(10) { - assertEquals(0, instance.computeZ(0, it, 5)) - } - } - - } - - } - - @Nested - inner class South { - - private lateinit var instance: MapImageMath - - @BeforeTest - fun onBefore() { - instance = MapImageMath.South - } - - @Test - fun `should have the correct yaw`() { - assertEquals(0f, instance.yaw) - } - - @Test - fun `should have the correct pitch`() { - assertEquals(0f, instance.pitch) - } - - @Nested - inner class ComputeX { - - @Test - fun `should always return 0 if no element per line`() { - repeat(10) { - assertThrows { - assertEquals(0, instance.computeX(0, it, itemFramesPerLine = 0)) - } - } - } - - @Test - fun `should compute the correct x with begin equals to 0`() { - val begin = 0 - val itemFramesPerLine = 3 - - assertEquals(0, instance.computeX(begin, 0, itemFramesPerLine)) - assertEquals(1, instance.computeX(begin, 1, itemFramesPerLine)) - assertEquals(2, instance.computeX(begin, 2, itemFramesPerLine)) - - assertEquals(0, instance.computeX(begin, 3, itemFramesPerLine)) - assertEquals(1, instance.computeX(begin, 4, itemFramesPerLine)) - assertEquals(2, instance.computeX(begin, 5, itemFramesPerLine)) - } - - @Test - fun `should compute the correct x with begin greater than 0`() { - val begin = 5 - val itemFramesPerLine = 3 - - assertEquals(5, instance.computeX(begin, 0, itemFramesPerLine)) - assertEquals(6, instance.computeX(begin, 1, itemFramesPerLine)) - assertEquals(7, instance.computeX(begin, 2, itemFramesPerLine)) - - assertEquals(5, instance.computeX(begin, 3, itemFramesPerLine)) - assertEquals(6, instance.computeX(begin, 4, itemFramesPerLine)) - assertEquals(7, instance.computeX(begin, 5, itemFramesPerLine)) - } - - } - - @Nested - inner class ComputeY { - - @Test - fun `should throws exception with no element per line`() { - repeat(10) { - assertThrows { - assertEquals(0, instance.computeY(0, it, itemFramesPerLine = 0)) - } - } - } - - @Test - fun `should compute the correct y with begin equals to 0`() { - val begin = 0 - val itemFramesPerLine = 3 - - assertEquals(0, instance.computeY(begin, 0, itemFramesPerLine)) - assertEquals(0, instance.computeY(begin, 1, itemFramesPerLine)) - assertEquals(0, instance.computeY(begin, 2, itemFramesPerLine)) - - assertEquals(-1, instance.computeY(begin, 3, itemFramesPerLine)) - assertEquals(-1, instance.computeY(begin, 4, itemFramesPerLine)) - assertEquals(-1, instance.computeY(begin, 5, itemFramesPerLine)) - - assertEquals(-2, instance.computeY(begin, 6, itemFramesPerLine)) - assertEquals(-2, instance.computeY(begin, 7, itemFramesPerLine)) - assertEquals(-2, instance.computeY(begin, 8, itemFramesPerLine)) - } - - @Test - fun `should compute the correct y with begin greater than 0`() { - val begin = 5 - val itemFramesPerLine = 3 - - assertEquals(5, instance.computeY(begin, 0, itemFramesPerLine)) - assertEquals(5, instance.computeY(begin, 1, itemFramesPerLine)) - assertEquals(5, instance.computeY(begin, 2, itemFramesPerLine)) - - assertEquals(4, instance.computeY(begin, 3, itemFramesPerLine)) - assertEquals(4, instance.computeY(begin, 4, itemFramesPerLine)) - assertEquals(4, instance.computeY(begin, 5, itemFramesPerLine)) - - assertEquals(3, instance.computeY(begin, 6, itemFramesPerLine)) - assertEquals(3, instance.computeY(begin, 7, itemFramesPerLine)) - assertEquals(3, instance.computeY(begin, 8, itemFramesPerLine)) - } - } - - @Nested - inner class ComputeZ { - - @Test - fun `should always return 0 if no element per line`() { - repeat(10) { - assertEquals(0, instance.computeZ(0, it, itemFramesPerLine = 0)) - } - } - - @ParameterizedTest - @ValueSource(ints = [-1, 0, 1]) - fun `should stay on the same z`(beginZ: Int) { - repeat(10) { - assertEquals(0, instance.computeZ(0, it, 5)) - } - } - } - } - - @Nested - inner class West { - - private lateinit var instance: MapImageMath - - @BeforeTest - fun onBefore() { - instance = MapImageMath.West - } - - @Test - fun `should have the correct yaw`() { - assertEquals(90f, instance.yaw) - } - - @Test - fun `should have the correct pitch`() { - assertEquals(0f, instance.pitch) - } - - @Nested - inner class ComputeX { - - @Test - fun `should always return 0 if no element per line`() { - repeat(10) { - assertEquals(0, instance.computeX(0, it, itemFramesPerLine = 0)) - } - } - - @ParameterizedTest - @ValueSource(ints = [-1, 0, 1]) - fun `should stay on the same x`(beginZ: Int) { - repeat(10) { - assertEquals(0, instance.computeX(0, it, 5)) - } - } - - } - - @Nested - inner class ComputeY { - - @Test - fun `should throws exception with no element per line`() { - repeat(10) { - assertThrows { - assertEquals(0, instance.computeY(0, it, itemFramesPerLine = 0)) - } - } - } - - @Test - fun `should compute the correct y with begin equals to 0`() { - val begin = 0 - val itemFramesPerLine = 3 - - assertEquals(0, instance.computeY(begin, 0, itemFramesPerLine)) - assertEquals(0, instance.computeY(begin, 1, itemFramesPerLine)) - assertEquals(0, instance.computeY(begin, 2, itemFramesPerLine)) - - assertEquals(-1, instance.computeY(begin, 3, itemFramesPerLine)) - assertEquals(-1, instance.computeY(begin, 4, itemFramesPerLine)) - assertEquals(-1, instance.computeY(begin, 5, itemFramesPerLine)) - - assertEquals(-2, instance.computeY(begin, 6, itemFramesPerLine)) - assertEquals(-2, instance.computeY(begin, 7, itemFramesPerLine)) - assertEquals(-2, instance.computeY(begin, 8, itemFramesPerLine)) - } - - @Test - fun `should compute the correct y with begin greater than 0`() { - val begin = 5 - val itemFramesPerLine = 3 - - assertEquals(5, instance.computeY(begin, 0, itemFramesPerLine)) - assertEquals(5, instance.computeY(begin, 1, itemFramesPerLine)) - assertEquals(5, instance.computeY(begin, 2, itemFramesPerLine)) - - assertEquals(4, instance.computeY(begin, 3, itemFramesPerLine)) - assertEquals(4, instance.computeY(begin, 4, itemFramesPerLine)) - assertEquals(4, instance.computeY(begin, 5, itemFramesPerLine)) - - assertEquals(3, instance.computeY(begin, 6, itemFramesPerLine)) - assertEquals(3, instance.computeY(begin, 7, itemFramesPerLine)) - assertEquals(3, instance.computeY(begin, 8, itemFramesPerLine)) - } - } - - @Nested - inner class ComputeZ { - - @Test - fun `should always return 0 if no element per line`() { - repeat(10) { - assertThrows { - assertEquals(0, instance.computeZ(0, it, itemFramesPerLine = 0)) - } - } - } - - @Test - fun `should compute the correct z with begin equals to 0`() { - val begin = 0 - val itemFramesPerLine = 3 - - assertEquals(0, instance.computeZ(begin, 0, itemFramesPerLine)) - assertEquals(1, instance.computeZ(begin, 1, itemFramesPerLine)) - assertEquals(2, instance.computeZ(begin, 2, itemFramesPerLine)) - - assertEquals(0, instance.computeZ(begin, 3, itemFramesPerLine)) - assertEquals(1, instance.computeZ(begin, 4, itemFramesPerLine)) - assertEquals(2, instance.computeZ(begin, 5, itemFramesPerLine)) - } - - @Test - fun `should compute the correct z with begin greater than 0`() { - val begin = 5 - val itemFramesPerLine = 3 - - assertEquals(5, instance.computeZ(begin, 0, itemFramesPerLine)) - assertEquals(6, instance.computeZ(begin, 1, itemFramesPerLine)) - assertEquals(7, instance.computeZ(begin, 2, itemFramesPerLine)) - - assertEquals(5, instance.computeZ(begin, 3, itemFramesPerLine)) - assertEquals(6, instance.computeZ(begin, 4, itemFramesPerLine)) - assertEquals(7, instance.computeZ(begin, 5, itemFramesPerLine)) - } - } - } - - @Nested - inner class East { - - private lateinit var instance: MapImageMath - - @BeforeTest - fun onBefore() { - instance = MapImageMath.East - } - - @Test - fun `should have the correct yaw`() { - assertEquals(270f, instance.yaw) - } - - @Test - fun `should have the correct pitch`() { - assertEquals(0f, instance.pitch) - } - - @Nested - inner class ComputeX { - - @Test - fun `should always return 0 if no element per line`() { - repeat(10) { - assertEquals(0, instance.computeX(0, it, itemFramesPerLine = 0)) - } - } - - @ParameterizedTest - @ValueSource(ints = [-1, 0, 1]) - fun `should stay on the same x`(beginZ: Int) { - repeat(10) { - assertEquals(0, instance.computeX(0, it, 5)) - } - } - - } - - @Nested - inner class ComputeY { - - @Test - fun `should throws exception with no element per line`() { - repeat(10) { - assertThrows { - assertEquals(0, instance.computeY(0, it, itemFramesPerLine = 0)) - } - } - } - - @Test - fun `should compute the correct y with begin equals to 0`() { - val begin = 0 - val itemFramesPerLine = 3 - - assertEquals(0, instance.computeY(begin, 0, itemFramesPerLine)) - assertEquals(0, instance.computeY(begin, 1, itemFramesPerLine)) - assertEquals(0, instance.computeY(begin, 2, itemFramesPerLine)) - - assertEquals(-1, instance.computeY(begin, 3, itemFramesPerLine)) - assertEquals(-1, instance.computeY(begin, 4, itemFramesPerLine)) - assertEquals(-1, instance.computeY(begin, 5, itemFramesPerLine)) - - assertEquals(-2, instance.computeY(begin, 6, itemFramesPerLine)) - assertEquals(-2, instance.computeY(begin, 7, itemFramesPerLine)) - assertEquals(-2, instance.computeY(begin, 8, itemFramesPerLine)) - } - - @Test - fun `should compute the correct y with begin greater than 0`() { - val begin = 5 - val itemFramesPerLine = 3 - - assertEquals(5, instance.computeY(begin, 0, itemFramesPerLine)) - assertEquals(5, instance.computeY(begin, 1, itemFramesPerLine)) - assertEquals(5, instance.computeY(begin, 2, itemFramesPerLine)) - - assertEquals(4, instance.computeY(begin, 3, itemFramesPerLine)) - assertEquals(4, instance.computeY(begin, 4, itemFramesPerLine)) - assertEquals(4, instance.computeY(begin, 5, itemFramesPerLine)) - - assertEquals(3, instance.computeY(begin, 6, itemFramesPerLine)) - assertEquals(3, instance.computeY(begin, 7, itemFramesPerLine)) - assertEquals(3, instance.computeY(begin, 8, itemFramesPerLine)) - } - - } - - @Nested - inner class ComputeZ { - - @Test - fun `should always return 0 if no element per line`() { - repeat(10) { - assertThrows { - assertEquals(0, instance.computeZ(0, it, itemFramesPerLine = 0)) - } - } - } - - @Test - fun `should compute the correct z with begin equals to 0`() { - val begin = 0 - val itemFramesPerLine = 3 - - assertEquals(0, instance.computeZ(begin, 0, itemFramesPerLine)) - assertEquals(-1, instance.computeZ(begin, 1, itemFramesPerLine)) - assertEquals(-2, instance.computeZ(begin, 2, itemFramesPerLine)) - - assertEquals(0, instance.computeZ(begin, 3, itemFramesPerLine)) - assertEquals(-1, instance.computeZ(begin, 4, itemFramesPerLine)) - assertEquals(-2, instance.computeZ(begin, 5, itemFramesPerLine)) - } - - @Test - fun `should compute the correct z with begin greater than 0`() { - val begin = 5 - val itemFramesPerLine = 3 - - assertEquals(5, instance.computeZ(begin, 0, itemFramesPerLine)) - assertEquals(4, instance.computeZ(begin, 1, itemFramesPerLine)) - assertEquals(3, instance.computeZ(begin, 2, itemFramesPerLine)) - - assertEquals(5, instance.computeZ(begin, 3, itemFramesPerLine)) - assertEquals(4, instance.computeZ(begin, 4, itemFramesPerLine)) - assertEquals(3, instance.computeZ(begin, 5, itemFramesPerLine)) - } - } - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/image/MapImageTest.kt b/src/test/kotlin/com/github/rushyverse/api/image/MapImageTest.kt deleted file mode 100644 index 33c541b5..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/image/MapImageTest.kt +++ /dev/null @@ -1,722 +0,0 @@ -package com.github.rushyverse.api.image - -import com.github.rushyverse.api.image.exception.ImageAlreadyLoadedException -import com.github.rushyverse.api.image.exception.ImageNotLoadedException -import com.github.rushyverse.api.image.exception.ItemFramesAlreadyExistException -import io.mockk.every -import io.mockk.spyk -import kotlinx.coroutines.test.runTest -import net.minestom.server.coordinate.Pos -import net.minestom.server.entity.metadata.other.ItemFrameMeta -import net.minestom.server.item.Material -import net.minestom.server.item.metadata.MapMeta -import net.minestom.server.network.packet.server.SendablePacket -import net.minestom.server.network.packet.server.play.MapDataPacket -import net.minestom.server.utils.Rotation -import net.minestom.testing.Env -import net.minestom.testing.EnvTest -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.assertThrows -import java.awt.Color -import java.awt.image.BufferedImage -import java.awt.image.BufferedImage.TYPE_INT_ARGB -import kotlin.test.* - -class MapImageTest { - - companion object { - private const val BLACK_COLOR_PACKET = 119.toByte() - } - - @Nested - @EnvTest - inner class CreateItemFrames { - - @Nested - inner class Position { - - @Test - fun `should spawn item frame at the target position`(env: Env) = runTest { - val instance = env.createFlatInstance() - val orientation = ItemFrameMeta.Orientation.NORTH - val math = MapImageMath.getFromOrientation(orientation) - val image = BufferedImage(128, 128, TYPE_INT_ARGB) - - repeat(10) { x -> - repeat(10) { y -> - repeat(10) { z -> - val mapImage = MapImage() - mapImage.loadImageAsPackets(image) - mapImage.createItemFrames( - instance, - Pos(x.toDouble(), y.toDouble(), z.toDouble()), - orientation - ) - assertEquals( - Pos(x.toDouble(), y.toDouble(), z.toDouble(), math.yaw, math.pitch), - mapImage.itemFrames!!.first().position - ) - } - } - } - } - - @Test - fun `should spawn item by following the north orientation`(env: Env) = runTest { - val instance = env.createFlatInstance() - val orientation = ItemFrameMeta.Orientation.NORTH - val math = MapImageMath.getFromOrientation(orientation) - val image = BufferedImage(256, 256, TYPE_INT_ARGB) - val mapImage = MapImage() - mapImage.loadImageAsPackets(image) - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), orientation) - - val itemFrames = mapImage.itemFrames!! - assertEquals(Pos(0.0, 0.0, 0.0, math.yaw, math.pitch), itemFrames[0].position) - assertEquals(Pos(-1.0, 0.0, 0.0, math.yaw, math.pitch), itemFrames[1].position) - assertEquals(Pos(0.0, -1.0, 0.0, math.yaw, math.pitch), itemFrames[2].position) - assertEquals(Pos(-1.0, -1.0, 0.0, math.yaw, math.pitch), itemFrames[3].position) - } - - @Test - fun `should spawn item by following the east orientation`(env: Env) = runTest { - val instance = env.createFlatInstance() - val orientation = ItemFrameMeta.Orientation.EAST - val math = MapImageMath.getFromOrientation(orientation) - val image = BufferedImage(256, 256, TYPE_INT_ARGB) - val mapImage = MapImage() - mapImage.loadImageAsPackets(image) - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), orientation) - - val itemFrames = mapImage.itemFrames!! - assertEquals(Pos(0.0, 0.0, 0.0, math.yaw, math.pitch), itemFrames[0].position) - assertEquals(Pos(0.0, 0.0, -1.0, math.yaw, math.pitch), itemFrames[1].position) - assertEquals(Pos(0.0, -1.0, 0.0, math.yaw, math.pitch), itemFrames[2].position) - assertEquals(Pos(0.0, -1.0, -1.0, math.yaw, math.pitch), itemFrames[3].position) - } - - @Test - fun `should spawn item by following the south orientation`(env: Env) = runTest { - val instance = env.createFlatInstance() - val orientation = ItemFrameMeta.Orientation.SOUTH - val math = MapImageMath.getFromOrientation(orientation) - val image = BufferedImage(256, 256, TYPE_INT_ARGB) - val mapImage = MapImage() - mapImage.loadImageAsPackets(image) - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), orientation) - - val itemFrames = mapImage.itemFrames!! - assertEquals(Pos(0.0, 0.0, 0.0, math.yaw, math.pitch), itemFrames[0].position) - assertEquals(Pos(1.0, 0.0, 0.0, math.yaw, math.pitch), itemFrames[1].position) - assertEquals(Pos(0.0, -1.0, 0.0, math.yaw, math.pitch), itemFrames[2].position) - assertEquals(Pos(1.0, -1.0, 0.0, math.yaw, math.pitch), itemFrames[3].position) - } - - @Test - fun `should spawn item by following the west orientation`(env: Env) = runTest { - val instance = env.createFlatInstance() - val orientation = ItemFrameMeta.Orientation.WEST - val math = MapImageMath.getFromOrientation(orientation) - val image = BufferedImage(256, 256, TYPE_INT_ARGB) - val mapImage = MapImage() - mapImage.loadImageAsPackets(image) - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), orientation) - - val itemFrames = mapImage.itemFrames!! - assertEquals(Pos(0.0, 0.0, 0.0, math.yaw, math.pitch), itemFrames[0].position) - assertEquals(Pos(0.0, 0.0, 1.0, math.yaw, math.pitch), itemFrames[1].position) - assertEquals(Pos(0.0, -1.0, 0.0, math.yaw, math.pitch), itemFrames[2].position) - assertEquals(Pos(0.0, -1.0, 1.0, math.yaw, math.pitch), itemFrames[3].position) - } - - @Test - fun `should spawn item by following the up orientation`(env: Env) = runTest { - val instance = env.createFlatInstance() - val orientation = ItemFrameMeta.Orientation.UP - val math = MapImageMath.getFromOrientation(orientation) - val image = BufferedImage(256, 256, TYPE_INT_ARGB) - val mapImage = MapImage() - mapImage.loadImageAsPackets(image) - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), orientation) - - val itemFrames = mapImage.itemFrames!! - assertEquals(Pos(0.0, 0.0, 0.0, math.yaw, math.pitch), itemFrames[0].position) - assertEquals(Pos(1.0, 0.0, 0.0, math.yaw, math.pitch), itemFrames[1].position) - assertEquals(Pos(0.0, 0.0, 1.0, math.yaw, math.pitch), itemFrames[2].position) - assertEquals(Pos(1.0, 0.0, 1.0, math.yaw, math.pitch), itemFrames[3].position) - } - - @Test - fun `should spawn item by following the down orientation`(env: Env) = runTest { - val instance = env.createFlatInstance() - val orientation = ItemFrameMeta.Orientation.DOWN - val math = MapImageMath.getFromOrientation(orientation) - val image = BufferedImage(256, 256, TYPE_INT_ARGB) - val mapImage = MapImage() - mapImage.loadImageAsPackets(image) - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), orientation) - - val itemFrames = mapImage.itemFrames!! - assertEquals(Pos(0.0, 0.0, 0.0, math.yaw, math.pitch), itemFrames[0].position) - assertEquals(Pos(1.0, 0.0, 0.0, math.yaw, math.pitch), itemFrames[1].position) - assertEquals(Pos(0.0, 0.0, -1.0, math.yaw, math.pitch), itemFrames[2].position) - assertEquals(Pos(1.0, 0.0, -1.0, math.yaw, math.pitch), itemFrames[3].position) - } - } - - @Nested - inner class MetaInformation { - - @Test - fun `should custom meta of item frame if needed`(env: Env) = runTest { - val instance = env.createFlatInstance() - val image = BufferedImage(128, 128, TYPE_INT_ARGB) - val mapImage = MapImage() - mapImage.loadImageAsPackets(image) - - val invisible = false - val rotation = Rotation.values().random() - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), ItemFrameMeta.Orientation.NORTH) { - this.isInvisible = false - this.rotation = rotation - } - - val itemFrame = mapImage.itemFrames!!.first() - val itemFrameMeta = itemFrame.entityMeta as ItemFrameMeta - assertEquals(rotation, itemFrameMeta.rotation) - assertEquals(invisible, itemFrameMeta.isInvisible) - } - - @Test - fun `should spawn item frame with the target orientation`(env: Env) = runTest { - val instance = env.createFlatInstance() - val image = BufferedImage(128, 128, TYPE_INT_ARGB) - - ItemFrameMeta.Orientation.values().forEach { orientation -> - val mapImage = MapImage() - mapImage.loadImageAsPackets(image) - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), orientation) - val itemFrame = mapImage.itemFrames!!.first() - assertEquals(orientation, (itemFrame.entityMeta as ItemFrameMeta).orientation) - } - } - - @Test - fun `should spawn item frame with invisibility by default`(env: Env) = runTest { - val instance = env.createFlatInstance() - val image = BufferedImage(128, 128, TYPE_INT_ARGB) - - val mapImage = MapImage() - mapImage.loadImageAsPackets(image) - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), orientation = ItemFrameMeta.Orientation.NORTH) - val itemFrame = mapImage.itemFrames!!.first() - assertEquals(true, itemFrame.isInvisible) - } - - @Test - fun `should spawn item frame with map item in meta`(env: Env) = runTest { - val instance = env.createFlatInstance() - val image = BufferedImage(128, 128, TYPE_INT_ARGB) - - val mapImage = MapImage() - mapImage.loadImageAsPackets(image) - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), orientation = ItemFrameMeta.Orientation.NORTH) - val itemFrame = mapImage.itemFrames!!.first() - val meta = itemFrame.entityMeta as ItemFrameMeta - - val metaItem = meta.item - assertNotNull(metaItem) - assertEquals(Material.FILLED_MAP, metaItem.material()) - } - - @Test - fun `should spawn item frame with map id`(env: Env) = runTest { - val instance = env.createFlatInstance() - val image = BufferedImage(256, 256, TYPE_INT_ARGB) - - val mapImage = MapImage() - mapImage.loadImageAsPackets(image) - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), orientation = ItemFrameMeta.Orientation.NORTH) - - val itemFrames = mapImage.itemFrames - assertNotNull(itemFrames) - assertEquals(4, itemFrames.size) - - itemFrames.forEachIndexed { index, entity -> - val meta = entity.entityMeta as ItemFrameMeta - val metaItem = meta.item - val metaOfMetaItem = metaItem.meta(MapMeta::class.java) - assertEquals(index, metaOfMetaItem.mapId) - } - } - - } - - @Nested - inner class WithImageNotLoaded { - - @Test - fun `should throw exception if image is not loaded`(env: Env) = runTest { - val instance = env.createFlatInstance() - val mapImage = MapImage() - val ex = assertThrows { - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), ItemFrameMeta.Orientation.NORTH) - } - assertEquals("An image must be loaded before creating the item frames.", ex.message) - } - } - - @Nested - inner class ItemFramesAlreadyExist { - - @Test - fun `should throw exception if all item frames already exist`(env: Env) = runTest { - val instance = env.createFlatInstance() - val image = BufferedImage(128, 128, TYPE_INT_ARGB) - val mapImage = MapImage() - mapImage.loadImageAsPackets(image) - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), ItemFrameMeta.Orientation.NORTH) - - val ex = assertThrows { - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), ItemFrameMeta.Orientation.NORTH) - } - - assertEquals("The item frames are already present in the instance.", ex.message) - } - - @Test - fun `should throw exception if at least one item frames already exist`(env: Env) = runTest { - val instance = env.createFlatInstance() - val image = BufferedImage(1024, 1024, TYPE_INT_ARGB) - val mapImage = MapImage() - mapImage.loadImageAsPackets(image) - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), ItemFrameMeta.Orientation.NORTH) - - val itemFrames = mapImage.itemFrames - assertNotNull(itemFrames) - itemFrames.drop(1).forEach { it.remove() } - assertTrue { itemFrames.drop(1).all { it.isRemoved } } - assertFalse { itemFrames.first().isRemoved } - - val ex = assertThrows { - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), ItemFrameMeta.Orientation.NORTH) - } - - assertEquals("The item frames are already present in the instance.", ex.message) - } - - } - - @Test - fun `should throw exception when image size is 0x0`(env: Env) = runTest { - val instance = env.createFlatInstance() - val mapImage = MapImage() - val image = BufferedImage(1, 1, TYPE_INT_ARGB) - mapImage.loadImageAsPackets(image) - - val spyMapImage = spyk(mapImage) { - every { itemFramesPerLine } returns 0 - every { itemFramesPerColumn } returns 0 - } - val returnedFrame = - spyMapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), ItemFrameMeta.Orientation.NORTH) - assertTrue { returnedFrame.isEmpty() } - assertTrue { spyMapImage.itemFrames!!.isEmpty() } - } - - @Test - fun `should return and set the property of item frames`(env: Env) = runTest { - val instance = env.createFlatInstance() - val mapImage = MapImage() - val image = BufferedImage(512, 1024, TYPE_INT_ARGB) - mapImage.loadImageAsPackets(image) - - val returnedFrame = mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), ItemFrameMeta.Orientation.NORTH) - assertEquals(returnedFrame, mapImage.itemFrames) - } - - @Test - fun `should create one item frame if image is between 1x1 and 128x128`(env: Env) = runTest { - val instance = env.createFlatInstance() - (1..128).forEach { width -> - val mapImage = MapImage() - mapImage.loadImageAsPackets(BufferedImage(width, width, TYPE_INT_ARGB)) - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), orientation = ItemFrameMeta.Orientation.NORTH) - - val itemFrames = mapImage.itemFrames - assertNotNull(itemFrames) - assertEquals(1, itemFrames.size) - } - } - - @Test - fun `should create two item frame if image is 129x128`(env: Env) = runTest { - val instance = env.createFlatInstance() - val mapImage = MapImage() - val orientation = ItemFrameMeta.Orientation.NORTH - mapImage.loadImageAsPackets(BufferedImage(129, 128, TYPE_INT_ARGB)) - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), orientation) - val math = MapImageMath.getFromOrientation(orientation) - - val itemFrames = mapImage.itemFrames - assertNotNull(itemFrames) - assertEquals(2, itemFrames.size) - - val (first, second) = itemFrames - assertEquals(Pos(0.0, 0.0, 0.0, math.yaw, math.pitch), first.position) - assertEquals(Pos(-1.0, 0.0, 0.0, math.yaw, math.pitch), second.position) - } - - @Test - fun `should create two item frame if image is 128x129`(env: Env) = runTest { - val instance = env.createFlatInstance() - val mapImage = MapImage() - val orientation = ItemFrameMeta.Orientation.NORTH - mapImage.loadImageAsPackets(BufferedImage(128, 129, TYPE_INT_ARGB)) - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), orientation) - val math = MapImageMath.getFromOrientation(orientation) - - val itemFrames = mapImage.itemFrames - assertNotNull(itemFrames) - assertEquals(2, itemFrames.size) - - val (first, second) = itemFrames - assertEquals(Pos(0.0, 0.0, 0.0, math.yaw, math.pitch), first.position) - assertEquals(Pos(0.0, -1.0, 0.0, math.yaw, math.pitch), second.position) - } - - @Test - fun `should spawn item frame at the target instance`(env: Env) = runTest { - val instance = env.createFlatInstance() - val mapImage = MapImage() - mapImage.loadImageAsPackets(BufferedImage(128, 128, TYPE_INT_ARGB)) - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), orientation = ItemFrameMeta.Orientation.NORTH) - assertEquals(instance, mapImage.itemFrames!!.first().instance) - } - } - - @Nested - inner class LoadImageAsPackets { - - @Nested - inner class ItemFramesPerLine { - - @Test - fun `should set property according to the width`() { - fun assertWidthPixelWithItemFramesPerLine(widths: IntRange, expectedFramesPerLine: Int) { - widths.forEach { - val image = BufferedImage(it, 128, TYPE_INT_ARGB) - val mapImage = MapImage() - mapImage.loadImageAsPackets(image) - assertEquals(expectedFramesPerLine, mapImage.itemFramesPerLine) - } - } - assertWidthPixelWithItemFramesPerLine(1..128, 1) - assertWidthPixelWithItemFramesPerLine(129..256, 2) - assertWidthPixelWithItemFramesPerLine(257..384, 3) - assertWidthPixelWithItemFramesPerLine(385..512, 4) - assertWidthPixelWithItemFramesPerLine(513..640, 5) - assertWidthPixelWithItemFramesPerLine(641..768, 6) - assertWidthPixelWithItemFramesPerLine(769..896, 7) - assertWidthPixelWithItemFramesPerLine(897..1024, 8) - } - - } - - @Nested - inner class ItemFramesPerColumn { - - @Test - fun `should set property according to the height`() { - fun assertHeightPixelWithItemFramesPerColumn(heights: IntRange, expectedFramesPerColumn: Int) { - heights.forEach { - val image = BufferedImage(128, it, TYPE_INT_ARGB) - val mapImage = MapImage() - mapImage.loadImageAsPackets(image) - assertEquals(expectedFramesPerColumn, mapImage.itemFramesPerColumn) - } - } - assertHeightPixelWithItemFramesPerColumn(1..128, 1) - assertHeightPixelWithItemFramesPerColumn(129..256, 2) - assertHeightPixelWithItemFramesPerColumn(257..384, 3) - assertHeightPixelWithItemFramesPerColumn(385..512, 4) - assertHeightPixelWithItemFramesPerColumn(513..640, 5) - assertHeightPixelWithItemFramesPerColumn(641..768, 6) - assertHeightPixelWithItemFramesPerColumn(769..896, 7) - assertHeightPixelWithItemFramesPerColumn(897..1024, 8) - } - - } - - @Test - fun `should throw exception if an image is already loaded`() { - val mapImage = MapImage() - mapImage.loadImageAsPackets(BufferedImage(128, 128, TYPE_INT_ARGB)) - assertThrows { - mapImage.loadImageAsPackets(BufferedImage(128, 128, TYPE_INT_ARGB)) - } - } - - @Test - fun `should load map data packets`() { - val mapImage = MapImage() - mapImage.loadImageAsPackets(BufferedImage(1000, 1000, TYPE_INT_ARGB)) - - val packets = assertNotNull(mapImage.packets) - packets.forEachIndexed { index, packet -> - assertTrue(packet is MapDataPacket) - assertEquals(index, packet.mapId) - } - } - - @Test - fun `should create one packet if the image is between 1x1 and 128x128`() { - (1..128).forEach { width -> - (1..128).forEach { height -> - val mapImage = MapImage() - mapImage.loadImageAsPackets(BufferedImage(width, height, TYPE_INT_ARGB)) - val packets = assertNotNull(mapImage.packets) - assertEquals(1, packets.size) - } - } - } - - @Test - fun `should create two packets if the image width is between 129 and 256`() { - (129..256).forEach { width -> - val mapImage = MapImage() - mapImage.loadImageAsPackets(BufferedImage(width, 1, TYPE_INT_ARGB)) - val packets = assertNotNull(mapImage.packets) - assertEquals(2, packets.size) - } - } - - @Test - fun `should create two packets if the image height is between 129 and 256`() { - (129..256).forEach { height -> - val mapImage = MapImage() - mapImage.loadImageAsPackets(BufferedImage(1, height, TYPE_INT_ARGB)) - val packets = assertNotNull(mapImage.packets) - assertEquals(2, packets.size) - } - } - - @Test - fun `should create four packets if the image is between 129x129 and 256x256`() { - fun assertNumberPackets(width: Int, height: Int, expectedNumberPackets: Int) { - val mapImage = MapImage() - mapImage.loadImageAsPackets(BufferedImage(width, height, TYPE_INT_ARGB)) - val packets = assertNotNull(mapImage.packets) - assertEquals(expectedNumberPackets, packets.size) - } - assertNumberPackets(129, 129, 4) - assertNumberPackets(256, 129, 4) - assertNumberPackets(129, 256, 4) - assertNumberPackets(200, 200, 4) - assertNumberPackets(256, 256, 4) - } - - @Test - fun `should have same content than the image for one packet`() { - val mapImage = MapImage() - val image = BufferedImage(128, 128, BufferedImage.TYPE_INT_RGB) - - val colors = listOf( - Color.PINK.rgb to (-110).toByte(), - Color.BLUE.rgb to (49).toByte(), - Color.GREEN.rgb to (-122).toByte() - ) - - for (y in 0 until image.height) { - for (x in 0 until image.width) { - val colorIndex = x % colors.size - image.setRGB(x, y, colors[colorIndex].first) - } - } - - mapImage.loadImageAsPackets(image) - val packets = assertNotNull(mapImage.packets) - assertEquals(1, packets.size) - val packet = packets[0] as MapDataPacket - - val data = packet.colorContent!!.data - assertEquals(128 * 128, data.size) - - for (y in 0 until 128) { - for (x in 0 until 128) { - val colorIndex = x % colors.size - assertEquals(colors[colorIndex].second, data[y * 128 + x]) - } - } - } - - @Test - fun `should have same content than the image for two packets`() { - val mapImage = MapImage() - val image = BufferedImage(256, 128, BufferedImage.TYPE_INT_RGB) - - val colorsFirstFrame = listOf( - Color.PINK.rgb to (-110).toByte(), - Color.BLUE.rgb to (49).toByte(), - Color.GREEN.rgb to (-122).toByte() - ) - - val colorsSecondFrame = listOf( - Color.DARK_GRAY.rgb to (85).toByte(), - Color.YELLOW.rgb to (74).toByte(), - Color.CYAN.rgb to (126).toByte() - ) - - // horizontal stripes - for (y in 0 until image.height) { - for (x in 0 until 128) { - val colorIndex = x % colorsFirstFrame.size - image.setRGB(x, y, colorsFirstFrame[colorIndex].first) - } - } - - // vertical stripes - for (x in 128 until 256) { - for (y in 0 until image.height) { - val colorIndex = x % colorsSecondFrame.size - image.setRGB(x, y, colorsSecondFrame[colorIndex].first) - } - } - - mapImage.loadImageAsPackets(image) - val packets = assertNotNull(mapImage.packets) - assertEquals(2, packets.size) - - val packet1 = packets[0] as MapDataPacket - val data1 = packet1.colorContent!!.data - assertTrue { data1.all { it != BLACK_COLOR_PACKET } } - assertEquals(128 * 128, data1.size) - - for (y in 0 until 128) { - for (x in 0 until image.height) { - val colorIndex = x % colorsFirstFrame.size - assertEquals(colorsFirstFrame[colorIndex].second, data1[y * 128 + x]) - } - } - - val packet2 = packets[1] as MapDataPacket - val data2 = packet2.colorContent!!.data - assertTrue { data2.all { it != BLACK_COLOR_PACKET } } - assertEquals(128 * 128, data2.size) - - for (x in 128 until 256) { - for (y in 0 until image.height) { - val colorIndex = x % colorsSecondFrame.size - assertEquals(colorsSecondFrame[colorIndex].second, data2[y * 128 + (x - 128)]) - } - } - } - - @Test - fun `should apply transformation on packets`() { - val mapImage = MapImage() - val image = BufferedImage(128, 128, BufferedImage.TYPE_INT_RGB) - - val colors = listOf( - Color.PINK.rgb to (-110).toByte(), - Color.BLUE.rgb to (49).toByte(), - Color.GREEN.rgb to (-122).toByte() - ) - - for (y in 0 until image.height) { - for (x in 0 until image.width) { - val colorIndex = x % colors.size - image.setRGB(x, y, colors[colorIndex].first) - } - } - - mapImage.loadImageAsPackets(image) { - rotate(Math.toRadians(90.0), it.width / 2.0, it.height / 2.0) - } - - val packets = assertNotNull(mapImage.packets) - assertEquals(1, packets.size) - val packet = packets[0] as MapDataPacket - - val data = packet.colorContent!!.data - assertEquals(128 * 128, data.size) - - for (x in 0 until 128) { - for (y in 0 until 128) { - // Use Y due to rotation - val colorIndex = y % colors.size - assertEquals(colors[colorIndex].second, data[y * 128 + x]) - } - } - } - - @Test - fun `should load image from resources`() { - val mapImage = MapImage() - val packets = mapImage.loadImageAsPacketsFromResources("map_image.png") - assertImagePacket(packets) - } - - @Test - fun `should load image from input stream`() { - val mapImage = MapImage() - MapImageTest::class.java.getResourceAsStream("/map_image.png")!!.buffered().use { - val packets = mapImage.loadImageAsPacketsFromInputStream(it) - assertImagePacket(packets) - } - } - - private fun assertImagePacket(packets: Array) { - val colors = listOf( - Color(84, 70, 162) to (23).toByte(), - Color(55, 215, 61) to (-122).toByte(), - Color(215, 55, 214) to (66).toByte(), - Color(49, 61, 50) to (84).toByte(), - ) - - assertNotNull(packets) - assertEquals(4, packets.size) - packets.zip(colors).forEach { (packet, color) -> - val data = (packet as MapDataPacket).colorContent!!.data - data.forEach { - assertEquals(color.second, it) - } - } - } - - } - - @Nested - @EnvTest - inner class RemoveItemFrames { - - @Test - fun `should do nothing if no item frames`(env: Env) { - val mapImage = MapImage() - assertNull(mapImage.itemFrames) - mapImage.removeItemFrames() - assertNull(mapImage.itemFrames) - } - - @Test - fun `should remove item frames`(env: Env) = runTest { - val instance = env.createFlatInstance() - val mapImage = MapImage() - mapImage.loadImageAsPackets(BufferedImage(512, 512, BufferedImage.TYPE_INT_RGB)) - mapImage.createItemFrames(instance, Pos(0.0, 0.0, 0.0), ItemFrameMeta.Orientation.NORTH) - - val frames = assertNotNull(mapImage.itemFrames) - - assertTrue { frames.all { !it.isRemoved } } - mapImage.removeItemFrames() - assertTrue { frames.all { it.isRemoved } } - - assertNull(mapImage.itemFrames) - } - } - - @Test - fun `constant value should be correct`() { - assertEquals(128, MapImage.MAP_ITEM_FRAME_PIXELS) - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/item/InventoryConditionSuspendTest.kt b/src/test/kotlin/com/github/rushyverse/api/item/InventoryConditionSuspendTest.kt deleted file mode 100644 index dd97df8e..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/item/InventoryConditionSuspendTest.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.github.rushyverse.api.item - -import com.github.rushyverse.api.utils.assertCoroutineContextFromScope -import com.github.rushyverse.api.utils.randomInt -import com.github.rushyverse.api.utils.randomString -import io.mockk.mockk -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.yield -import net.minestom.server.entity.Player -import net.minestom.server.inventory.click.ClickType -import net.minestom.server.inventory.condition.InventoryConditionResult -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Timeout -import java.util.concurrent.TimeUnit -import kotlin.coroutines.coroutineContext -import kotlin.test.Test -import kotlin.test.assertEquals - -@Timeout(5, unit = TimeUnit.SECONDS) -class InventoryConditionSuspendTest { - - @Nested - inner class AsNative { - - @Test - fun `should use params sent to the native inventory`() { - val expectedPlayer = mockk(randomString()) - val expectedSlot = randomInt() - val expectedClickType = mockk(randomString()) - val expectedInventoryConditionResult = mockk(randomString()) - - val inventoryConditionSuspend = InventoryConditionSuspend { player, clickedSlot, clickType, result -> - assertEquals(expectedPlayer, player) - assertEquals(expectedSlot, clickedSlot) - assertEquals(expectedClickType, clickType) - assertEquals(expectedInventoryConditionResult, result) - } - - val coroutineScope = CoroutineScope(Dispatchers.Default) - val inventoryCondition = inventoryConditionSuspend.asNative(coroutineScope) - inventoryCondition.accept(expectedPlayer, expectedSlot, expectedClickType, expectedInventoryConditionResult) - } - - @Test - fun `should stay in current thread before suspend point`() { - val thread = Thread.currentThread().id - val inventoryConditionSuspend = InventoryConditionSuspend { _, _, _, _ -> - assertEquals(thread, Thread.currentThread().id) - } - - val coroutineScope = CoroutineScope(Dispatchers.Default) - val inventoryCondition = inventoryConditionSuspend.asNative(coroutineScope) - inventoryCondition.accept(mockk(), 0, mockk(), mockk()) - } - - @Test - fun `should change thread context after suspend point`() { - val coroutineScope = CoroutineScope(Dispatchers.Default) - - val inventoryConditionSuspend = InventoryConditionSuspend { _, _, _, _ -> - yield() - assertCoroutineContextFromScope(coroutineScope, coroutineContext) - } - - val inventoryCondition = inventoryConditionSuspend.asNative(coroutineScope) - inventoryCondition.accept(mockk(), 0, mockk(), mockk()) - } - - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/item/ItemComparatorTest.kt b/src/test/kotlin/com/github/rushyverse/api/item/ItemComparatorTest.kt deleted file mode 100644 index 51ad6a3a..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/item/ItemComparatorTest.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.github.rushyverse.api.item - -import net.kyori.adventure.text.Component -import net.minestom.server.item.ItemStack -import net.minestom.server.item.Material -import org.junit.jupiter.api.Test -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class ItemComparatorTest { - - @Test - fun `similar should check the similarity without the amount`() { - val item1 = ItemStack.of(Material.DIAMOND) - val item2 = ItemStack.of(Material.DIAMOND) - assertTrue(ItemComparator.SIMILAR.areSame(item1, item2)) - - val item3 = ItemStack.of(Material.DIAMOND).withDisplayName(Component.text("Test")) - assertFalse(ItemComparator.SIMILAR.areSame(item1, item3)) - - val item4 = ItemStack.of(Material.DIAMOND, 2).withDisplayName(Component.text("Test")) - assertTrue(ItemComparator.SIMILAR.areSame(item3, item4)) - - val item5 = ItemStack.of(Material.DIAMOND_SWORD) - assertFalse(ItemComparator.SIMILAR.areSame(item1, item5)) - } - - @Test - fun `equals should check the similarity with the amount`() { - val item1 = ItemStack.of(Material.DIAMOND) - val item2 = ItemStack.of(Material.DIAMOND) - assertTrue(ItemComparator.EQUALS.areSame(item1, item2)) - - val item3 = ItemStack.of(Material.DIAMOND).withDisplayName(Component.text("Test")) - assertFalse(ItemComparator.EQUALS.areSame(item1, item3)) - - val item4 = ItemStack.of(Material.DIAMOND, 2).withDisplayName(Component.text("Test")) - assertFalse(ItemComparator.EQUALS.areSame(item3, item4)) - - val item5 = ItemStack.of(Material.DIAMOND_SWORD) - assertFalse(ItemComparator.EQUALS.areSame(item1, item5)) - } - - @Test - fun `custom should respect the custom comparator`() { - val comparator = ItemComparator { a, b -> a.material() == b.material() } - - val item1 = ItemStack.of(Material.DIAMOND) - val item2 = ItemStack.of(Material.DIAMOND) - assertTrue(comparator.areSame(item1, item2)) - - val item3 = ItemStack.of(Material.DIAMOND, 2) - assertTrue(comparator.areSame(item1, item3)) - - val item4 = ItemStack.of(Material.DIAMOND).withDisplayName(Component.text("Test")) - assertTrue(comparator.areSame(item1, item4)) - - val item5 = ItemStack.of(Material.DIAMOND_SWORD) - assertFalse(comparator.areSame(item1, item5)) - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/listener/EventListenerSuspendTest.kt b/src/test/kotlin/com/github/rushyverse/api/listener/EventListenerSuspendTest.kt deleted file mode 100644 index 5d967174..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/listener/EventListenerSuspendTest.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.github.rushyverse.api.listener - -import com.github.rushyverse.api.utils.assertCoroutineContextFromScope -import io.mockk.mockk -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.yield -import net.minestom.server.event.EventListener -import net.minestom.server.event.player.PlayerLoginEvent -import net.minestom.server.event.player.PlayerMoveEvent -import java.util.concurrent.CountDownLatch -import kotlin.coroutines.coroutineContext -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotEquals -import kotlin.test.assertTrue - -class EventListenerSuspendTest { - - @Test - fun `should use dispatcher to process event listener suspend`() { - val expectedEvent = mockk() - val currentThread = Thread.currentThread() - val latch = CountDownLatch(1) - var executed = false - - val scope = CoroutineScope(Dispatchers.Default) - val handler = object : EventListenerSuspend(scope) { - override suspend fun runSuspend(event: PlayerMoveEvent) { - assertCoroutineContextFromScope(scope, coroutineContext) - assertEquals(currentThread, Thread.currentThread()) - assertEquals(expectedEvent, event) - - yield() - - assertNotEquals(currentThread, Thread.currentThread()) - latch.countDown() - executed = true - } - - override fun eventType(): Class { - return PlayerMoveEvent::class.java - } - } - - assertEquals(EventListener.Result.SUCCESS, handler.run(expectedEvent)) - latch.await() - assertTrue(executed) - } - - @Test - fun `should return success despite an exception`() { - val expectedEvent = mockk() - val scope = CoroutineScope(Dispatchers.Default) - val handler = object : EventListenerSuspend(scope) { - override suspend fun runSuspend(event: PlayerLoginEvent) { - throw RuntimeException() - } - - override fun eventType(): Class { - return PlayerLoginEvent::class.java - } - } - - assertEquals(EventListener.Result.SUCCESS, handler.run(expectedEvent)) - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/listener/NPCListenerBuilderTest.kt b/src/test/kotlin/com/github/rushyverse/api/listener/NPCListenerBuilderTest.kt deleted file mode 100644 index b1f52485..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/listener/NPCListenerBuilderTest.kt +++ /dev/null @@ -1,77 +0,0 @@ -package com.github.rushyverse.api.listener - -import com.github.rushyverse.api.entity.NPCEntity -import io.mockk.every -import io.mockk.justRun -import io.mockk.mockk -import io.mockk.verify -import net.minestom.server.entity.Player -import net.minestom.server.event.Event -import net.minestom.server.event.EventNode -import net.minestom.server.event.player.PlayerEntityInteractEvent -import org.junit.jupiter.api.Nested -import kotlin.test.BeforeTest -import kotlin.test.Test - -class NPCListenerBuilderTest { - - lateinit var node: EventNode - - @BeforeTest - fun onBefore() { - node = NPCListenerBuilder.createEventNode() - } - - @Nested - inner class CreateEventNode { - - @Test - fun `create event node with npc name`() { - assert(node.name == "npc") - } - - } - - @Nested - inner class AddInteractListener { - - @Test - fun `node event has a register listener for interact event`() { - node.hasListener(PlayerEntityInteractEvent::class.java) - } - - @Test - fun `doesn't trigger when target is not an npc`() { - val event = mockk() - every { event.target } returns mockk() - node.call(event) - verify(exactly = 1) { event.target } - } - - @Test - fun `doesn't trigger when hand is not main hand`() { - val event = mockk() - every { event.target } returns mockk() - every { event.hand } returns Player.Hand.OFF - node.call(event) - verify(exactly = 1) { event.target } - verify(exactly = 1) { event.hand } - } - - @Test - fun `trigger interaction method of npc`() { - val event = mockk() - val target = mockk() - justRun { target.onInteract(event) } - - every { event.target } returns target - every { event.hand } returns Player.Hand.MAIN - - node.call(event) - verify(exactly = 2) { event.target } - verify(exactly = 1) { event.hand } - verify(exactly = 1) { target.onInteract(event) } - } - - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/position/AbstractAreaTest.kt b/src/test/kotlin/com/github/rushyverse/api/position/AbstractAreaTest.kt deleted file mode 100644 index 7c93ba96..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/position/AbstractAreaTest.kt +++ /dev/null @@ -1,104 +0,0 @@ -package com.github.rushyverse.api.position - -import com.github.rushyverse.api.utils.randomString -import io.mockk.mockk -import net.minestom.server.entity.Entity -import org.junit.jupiter.api.Nested -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -class AbstractAreaTest { - - @Nested - inner class Instantiation { - - @Test - fun `should have no entities at the creation`() { - val area = object : AbstractArea() { - override fun updateEntitiesInArea(): Pair, Collection> { - error("Not implemented") - } - } - assertTrue { area.entitiesInArea.isEmpty() } - } - } - - @Nested - inner class Update { - - @Test - fun `should add all entities if list is empty`() { - val entities = setOf( - mockk(randomString()), - mockk(randomString()), - mockk(randomString()) - ) - val area = object : AbstractArea() { - override fun updateEntitiesInArea(): Pair, Collection> { - return update(entities) - } - } - - val (added, removed) = area.updateEntitiesInArea() - assertEquals(entities, added) - assertTrue { removed.isEmpty() } - assertEquals(entities, area.entitiesInArea) - } - - @Test - fun `should add and remove all entities if list not in entities list of area`() { - val entity1 = mockk(randomString()) - val entity2 = mockk(randomString()) - val entity3 = mockk(randomString()) - - var entities = setOf(entity1, entity2, entity3) - - val area = object : AbstractArea() { - override fun updateEntitiesInArea(): Pair, Collection> { - return update(entities) - } - } - area.updateEntitiesInArea() - - area.updateEntitiesInArea() - entities = setOf(entity1, entity2) - val (added, removed) = area.updateEntitiesInArea() - assertTrue { added.isEmpty() } - assertEquals(setOf(entity3), removed) - assertEquals(entities, area.entitiesInArea) - - val entity4 = mockk(randomString()) - entities = setOf(entity1, entity4) - val (added2, removed2) = area.updateEntitiesInArea() - assertEquals(setOf(entity4), added2) - assertEquals(setOf(entity2), removed2) - assertEquals(entities, area.entitiesInArea) - } - - @Test - fun `should not change entities in area if entities is always in area`() { - val entity1 = mockk(randomString()) - val entity2 = mockk(randomString()) - val entity3 = mockk(randomString()) - - val entities = setOf(entity1, entity2, entity3) - - val area = object : AbstractArea() { - override fun updateEntitiesInArea(): Pair, Collection> { - return update(entities) - } - } - val (added, removed) = area.updateEntitiesInArea() - assertEquals(entities, added) - assertTrue { removed.isEmpty() } - assertEquals(entities, area.entitiesInArea) - - val (added2, removed2) = area.updateEntitiesInArea() - assertTrue { added2.isEmpty() } - assertTrue { removed2.isEmpty() } - assertEquals(entities, area.entitiesInArea) - } - - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/position/CubeAreaTest.kt b/src/test/kotlin/com/github/rushyverse/api/position/CubeAreaTest.kt deleted file mode 100644 index 8e7e907f..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/position/CubeAreaTest.kt +++ /dev/null @@ -1,260 +0,0 @@ -package com.github.rushyverse.api.position - -import com.github.rushyverse.api.utils.randomPos -import com.github.rushyverse.api.utils.randomString -import io.mockk.every -import io.mockk.mockk -import net.minestom.server.coordinate.Pos -import net.minestom.server.entity.Entity -import net.minestom.server.entity.Player -import net.minestom.server.instance.Instance -import org.junit.jupiter.api.Nested -import kotlin.test.Test -import kotlin.test.assertContentEquals -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -class CubeAreaTest { - - @Nested - inner class Instantiation { - - @Test - fun `should have no entities at the creation`() { - val area = CubeArea(mockk(), randomPos(), randomPos()) - assertTrue { area.entitiesInArea.isEmpty() } - } - - @Test - fun `should have the correct min and max positions`() { - val min = Pos(0.0, 10.0, -10.0) - val max = Pos(-20.0, 11.0, -16.0) - val area = CubeArea(mockk(), min, max) - assertEquals(Pos(-20.0, 10.0, -16.0), area.min) - assertEquals(Pos(0.0, 11.0, -10.0), area.max) - } - - @Test - fun `should have the correct min and max positions if min and max are already ordered`() { - val min = Pos(-1.0, -2.0, -3.0) - val max = Pos(0.0, 1.0, 2.0) - val area = CubeArea(mockk(), min, max) - assertEquals(min, area.min) - assertEquals(max, area.max) - } - - @Test - fun `position should be the center of the area`() { - val min = Pos(0.0, 10.0, -10.0) - val max = Pos(-20.0, 11.0, -16.0) - val area = CubeArea(mockk(), min, max) - assertEquals(Pos(-10.0, 10.5, -13.0), area.position) - } - - } - - @Nested - inner class SetPosition { - - @Test - fun `should keep the same position if the new value is the same`() { - val area = CubeArea(mockk(), Pos(0.0, 0.0, 0.0), Pos(10.5, 10.5, 10.5)) - val oldMin = area.min - val oldMax = area.max - area.position = area.position - assertEquals(oldMin, area.min) - assertEquals(oldMax, area.max) - } - - @Test - fun `should change the position if the new positive value is different`() { - val min = Pos(0.0, 0.0, 0.0) - val max = Pos(10.0, 10.0, 10.0) - val area = CubeArea(mockk(), min, max) - val newPosition = Pos(20.0, 20.0, 20.0) - area.position = newPosition - assertEquals(newPosition, area.position) - assertEquals(Pos(15.0, 15.0, 15.0), area.min) - assertEquals(Pos(25.0, 25.0, 25.0), area.max) - } - - @Test - fun `should change the position if the new negative value is different`() { - val min = Pos(0.0, 0.0, 0.0) - val max = Pos(10.0, 10.0, 10.0) - val area = CubeArea(mockk(), min, max) - val newPosition = Pos(-20.0, -20.0, -20.0) - area.position = newPosition - assertEquals(newPosition, area.position) - assertEquals(Pos(-25.0, -25.0, -25.0), area.min) - assertEquals(Pos(-15.0, -15.0, -15.0), area.max) - } - - @Test - fun `should change the position if the new mixed value is different`() { - val min = Pos(0.0, 0.0, 0.0) - val max = Pos(10.0, 10.0, 10.0) - val area = CubeArea(mockk(), min, max) - val newPosition = Pos(20.0, -20.0, -20.0) - area.position = newPosition - assertEquals(newPosition, area.position) - assertEquals(Pos(15.0, -25.0, -25.0), area.min) - assertEquals(Pos(25.0, -15.0, -15.0), area.max) - } - - } - - @Nested - inner class GetPosition { - - @Test - fun `should return the center of the area with positive values`() { - val min = Pos(10.0, 10.0, 10.0) - val max = Pos(20.0, 20.0, 20.0) - val area = CubeArea(mockk(), min, max) - assertEquals(Pos(15.0, 15.0, 15.0), area.position) - } - - @Test - fun `should return the center of the area with negative values`() { - val min = Pos(-20.0, -20.0, -20.0) - val max = Pos(-10.0, -10.0, -10.0) - val area = CubeArea(mockk(), min, max) - assertEquals(Pos(-15.0, -15.0, -15.0), area.position) - } - - @Test - fun `should return the center of the area with mixed values`() { - val min = Pos(-20.0, 10.0, -20.0) - val max = Pos(-10.0, 20.0, -10.0) - val area = CubeArea(mockk(), min, max) - assertEquals(Pos(-15.0, 15.0, -15.0), area.position) - } - - @Test - fun `should return the center of the area with decimal value`() { - val min = Pos(10.6, 10.8, 10.4) - val max = Pos(10.0, 10.0, 20.0) - val area = CubeArea(mockk(), min, max) - assertEquals(Pos(10.3, 10.4, 15.2), area.position) - } - } - - @Nested - inner class UpdateEntitiesInArea { - - @Test - fun `should have filtered entities on type`() { - val player = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val player2 = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val entity = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val instance = mockk(randomString()) { - every { entities } returns setOf(player, player2, entity) - } - - val min = Pos(0.0, 0.0, 0.0) - val area = CubeArea(instance, min, min) - val (added, removed) = area.updateEntitiesInArea() - - assertContentEquals(listOf(player, player2), added) - assertContentEquals(emptyList(), removed) - assertContentEquals(listOf(player, player2), area.entitiesInArea) - } - - @Test - fun `should have entities in the area`() { - val player = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val entity = mockk(randomString()) { - every { position } returns Pos(10.0, 10.0, 10.0) - } - val entity2 = mockk(randomString()) { - every { position } returns Pos(11.0, 5.0, 5.0) - } - val instance = mockk(randomString()) { - every { entities } returns setOf(player, entity, entity2) - } - - val min = Pos(0.0, 0.0, 0.0) - val max = Pos(10.0, 10.0, 10.0) - val area = CubeArea(instance, min, max) - val (added, removed) = area.updateEntitiesInArea() - - assertContentEquals(listOf(player, entity), added) - assertContentEquals(emptyList(), removed) - assertContentEquals(listOf(player, entity), area.entitiesInArea) - } - - @Test - fun `should remove entities in the area`() { - val player = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val entity = mockk(randomString()) { - every { position } returns Pos(10.0, 10.0, 10.0) - } - val entity2 = mockk(randomString()) { - every { position } returns Pos(11.0, 5.0, 5.0) - } - val instance = mockk(randomString()) { - every { entities } returns setOf(player, entity, entity2) - } - - val min = Pos(0.0, 0.0, 0.0) - val max = Pos(10.0, 10.0, 10.0) - val area = CubeArea(instance, min, max) - val (added, removed) = area.updateEntitiesInArea() - - assertContentEquals(listOf(player, entity), added) - assertContentEquals(emptyList(), removed) - assertContentEquals(listOf(player, entity), area.entitiesInArea) - - every { player.position } returns Pos(11.0, 5.0, 5.0) - every { entity2.position } returns Pos(0.0, 6.0, 0.0) - - val (added2, removed2) = area.updateEntitiesInArea() - assertContentEquals(listOf(entity2), added2) - assertContentEquals(listOf(player), removed2) - assertContentEquals(listOf(entity, entity2), area.entitiesInArea) - } - - @Test - fun `should not change entities in area if entities is always in area`() { - val player = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val entity = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val entity2 = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val instance = mockk(randomString()) { - every { entities } returns setOf(player, entity, entity2) - } - - val min = Pos(0.0, 0.0, 0.0) - val max = Pos(10.0, 10.0, 10.0) - val area = CubeArea(instance, min, max) - val (added, removed) = area.updateEntitiesInArea() - - assertContentEquals(listOf(player, entity, entity2), added) - assertContentEquals(emptyList(), removed) - assertContentEquals(listOf(player, entity, entity2), area.entitiesInArea) - - val (added2, removed2) = area.updateEntitiesInArea() - assertContentEquals(emptyList(), added2) - assertContentEquals(emptyList(), removed2) - assertContentEquals(listOf(player, entity, entity2), area.entitiesInArea) - } - - } - -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/position/CylinderAreaTest.kt b/src/test/kotlin/com/github/rushyverse/api/position/CylinderAreaTest.kt deleted file mode 100644 index feecc865..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/position/CylinderAreaTest.kt +++ /dev/null @@ -1,353 +0,0 @@ -package com.github.rushyverse.api.position - -import com.github.rushyverse.api.utils.randomPos -import com.github.rushyverse.api.utils.randomString -import io.mockk.every -import io.mockk.mockk -import net.minestom.server.coordinate.Pos -import net.minestom.server.entity.Entity -import net.minestom.server.entity.Player -import net.minestom.server.instance.Instance -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.assertThrows -import kotlin.test.Test -import kotlin.test.assertContentEquals -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -class CylinderAreaTest { - - @Nested - inner class Instantiation { - - @Test - fun `should have no entities at the creation`() { - val area = CylinderArea(mockk(), randomPos(), 0.0, 0.0..0.0) - assertTrue { area.entitiesInArea.isEmpty() } - } - - @Test - fun `should throw an exception if the radius is negative`() { - assertThrows { - CylinderArea(mockk(), randomPos(), -1.0, 0.0..0.0) - } - } - - @Test - fun `should throw an exception if the radius is set`() { - val area = CylinderArea(mockk(), randomPos(), 0.0, 0.0..0.0) - assertThrows { - area.radius = -1.0 - } - } - - @Test - fun `should set the radius without exception if value is zero or positive`() { - val area = CylinderArea(mockk(), randomPos(), 0.0, 0.0..0.0) - - area.radius = 0.0 - assertEquals(0.0, area.radius) - - area.radius = 1.0 - assertEquals(1.0, area.radius) - } - } - - @Nested - inner class UpdateWithYChange { - - @Test - fun `should use negative y limit`() { - val player = mockk(randomString()) { - every { position } returns Pos(0.0, -5.0, 0.0) - } - val player2 = mockk(randomString()) { - every { position } returns Pos(0.0, -8.1, 0.0) - } - val player3 = mockk(randomString()) { - every { position } returns Pos(0.0, -11.0, 0.0) - } - val player4 = mockk(randomString()) { - every { position } returns Pos(0.0, -4.9, 0.0) - } - val instance = mockk(randomString()) { - every { entities } returns setOf(player, player2, player3, player4) - } - - val min = Pos(0.0, 0.0, 0.0) - val area = CylinderArea(instance, min, 1.0, -10.0..-5.0) - val (added, removed) = area.updateEntitiesInArea() - - assertContentEquals(listOf(player, player2), added) - assertContentEquals(emptyList(), removed) - assertContentEquals(listOf(player, player2), area.entitiesInArea) - } - - @Test - fun `should use positive y limit`() { - val player = mockk(randomString()) { - every { position } returns Pos(0.0, 5.0, 0.0) - } - val player2 = mockk(randomString()) { - every { position } returns Pos(0.0, 7.3, 0.0) - } - val player3 = mockk(randomString()) { - every { position } returns Pos(0.0, 4.3, 0.0) - } - val player4 = mockk(randomString()) { - every { position } returns Pos(0.0, 10.1, 0.0) - } - val instance = mockk(randomString()) { - every { entities } returns setOf(player, player2, player3, player4) - } - - val min = Pos(0.0, 0.0, 0.0) - val area = CylinderArea(instance, min, 0.0, 5.0..10.0) - val (added, removed) = area.updateEntitiesInArea() - - assertContentEquals(listOf(player, player2), added) - assertContentEquals(emptyList(), removed) - assertContentEquals(listOf(player, player2), area.entitiesInArea) - } - - @Test - fun `should use negative and positive y limit`() { - val player = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val player2 = mockk(randomString()) { - every { position } returns Pos(0.0, -3.0, 0.0) - } - val player3 = mockk(randomString()) { - every { position } returns Pos(0.0, 8.0, 0.0) - } - val player4 = mockk(randomString()) { - every { position } returns Pos(0.0, 10.1, 0.0) - } - val player5 = mockk(randomString()) { - every { position } returns Pos(0.0, -5.1, 0.0) - } - val instance = mockk(randomString()) { - every { entities } returns setOf(player, player2, player3, player4, player5) - } - - val min = Pos(0.0, 0.0, 0.0) - val area = CylinderArea(instance, min, 0.0, -5.0..10.0) - val (added, removed) = area.updateEntitiesInArea() - - assertContentEquals(listOf(player, player2, player3), added) - assertContentEquals(emptyList(), removed) - assertContentEquals(listOf(player, player2, player3), area.entitiesInArea) - } - - @Test - fun `should use zero y limit`() { - val player = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val player2 = mockk(randomString()) { - every { position } returns Pos(0.0, -0.1, 0.0) - } - val player3 = mockk(randomString()) { - every { position } returns Pos(0.0, 0.1, 0.0) - } - val instance = mockk(randomString()) { - every { entities } returns setOf(player, player2, player3) - } - - val min = Pos(0.0, 0.0, 0.0) - val area = CylinderArea(instance, min, 0.0, 0.0..0.0) - val (added, removed) = area.updateEntitiesInArea() - - assertContentEquals(listOf(player), added) - assertContentEquals(emptyList(), removed) - assertContentEquals(listOf(player), area.entitiesInArea) - } - - } - - @Nested - inner class UpdateWithRadiusChange { - - @Test - fun `should use zero for radius`() { - val player = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val player2 = mockk(randomString()) { - every { position } returns Pos(0.1, 0.0, 0.0) - } - val player3 = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.1) - } - val player4 = mockk(randomString()) { - every { position } returns Pos(-0.1, 0.0, 0.0) - } - val player5 = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, -0.1) - } - val instance = mockk(randomString()) { - every { entities } returns setOf(player, player2, player3, player4, player5) - } - - val min = Pos(0.0, 0.0, 0.0) - val area = CylinderArea(instance, min, 0.0, 0.0..0.0) - val (added, removed) = area.updateEntitiesInArea() - - assertContentEquals(listOf(player), added) - assertContentEquals(emptyList(), removed) - assertContentEquals(listOf(player), area.entitiesInArea) - } - - @Test - fun `should use positive for radius`() { - val player = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val player2 = mockk(randomString()) { - every { position } returns Pos(1.0, 0.0, 0.0) - } - val player3 = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 1.0) - } - val player4 = mockk(randomString()) { - every { position } returns Pos(-1.0, 0.0, 0.0) - } - val player5 = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, -1.0) - } - val player6 = mockk(randomString()) { - every { position } returns Pos(1.0, 0.0, 0.1) - } - val player7 = mockk(randomString()) { - every { position } returns Pos(-1.0, 0.0, -0.1) - } - val instance = mockk(randomString()) { - every { entities } returns setOf(player, player2, player3, player4, player5, player6, player7) - } - - val min = Pos(0.0, 0.0, 0.0) - val area = CylinderArea(instance, min, 1.0, 0.0..0.0) - val (added, removed) = area.updateEntitiesInArea() - - assertContentEquals(listOf(player, player2, player3, player4, player5), added) - assertContentEquals(emptyList(), removed) - assertContentEquals(listOf(player, player2, player3, player4, player5), area.entitiesInArea) - } - } - - @Nested - inner class UpdateEntitiesInArea { - - @Test - fun `should have filtered entities on type`() { - val player = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val player2 = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val entity = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val instance = mockk(randomString()) { - every { entities } returns setOf(player, player2, entity) - } - - val min = Pos(0.0, 0.0, 0.0) - val area = CylinderArea(instance, min, 1.0, 0.0..0.0) - val (added, removed) = area.updateEntitiesInArea() - - assertContentEquals(listOf(player, player2), added) - assertContentEquals(emptyList(), removed) - assertContentEquals(listOf(player, player2), area.entitiesInArea) - } - - @Test - fun `should have entities in the area`() { - val player = mockk(randomString()) { - every { position } returns Pos(1.0, 2.0, 4.0) - } - val entity = mockk(randomString()) { - every { position } returns Pos(2.0, 2.0, 4.0) - } - val entity2 = mockk(randomString()) { - every { position } returns Pos(5.0, 5.0, 5.0) - } - val instance = mockk(randomString()) { - every { entities } returns setOf(player, entity, entity2) - } - - val min = Pos(0.0, 0.0, 0.0) - val area = CylinderArea(instance, min, 5.0, 0.0..2.0) - val (added, removed) = area.updateEntitiesInArea() - - assertContentEquals(listOf(player, entity), added) - assertContentEquals(emptyList(), removed) - assertContentEquals(listOf(player, entity), area.entitiesInArea) - } - - @Test - fun `should remove entities in the area`() { - val player = mockk(randomString()) { - every { position } returns Pos(1.0, 2.0, 4.0) - } - val entity = mockk(randomString()) { - every { position } returns Pos(2.0, 2.0, 4.0) - } - val entity2 = mockk(randomString()) { - every { position } returns Pos(5.0, 5.0, 5.0) - } - val instance = mockk(randomString()) { - every { entities } returns setOf(player, entity, entity2) - } - - val min = Pos(0.0, 0.0, 0.0) - val area = CylinderArea(instance, min, 5.0, 0.0..3.0) - val (added, removed) = area.updateEntitiesInArea() - - assertContentEquals(listOf(player, entity), added) - assertContentEquals(emptyList(), removed) - assertContentEquals(listOf(player, entity), area.entitiesInArea) - - every { player.position } returns Pos(10.0, 0.0, 0.0) - every { entity2.position } returns Pos(0.0, 1.0, 0.0) - - val (added2, removed2) = area.updateEntitiesInArea() - assertContentEquals(listOf(entity2), added2) - assertContentEquals(listOf(player), removed2) - assertContentEquals(listOf(entity, entity2), area.entitiesInArea) - } - - @Test - fun `should not change entities in area if entities is always in area`() { - val player = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val entity = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val entity2 = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val instance = mockk(randomString()) { - every { entities } returns setOf(player, entity, entity2) - } - - val min = Pos(0.0, 0.0, 0.0) - val area = CylinderArea(instance, min, 5.0, 0.0..3.0) - val (added, removed) = area.updateEntitiesInArea() - - assertContentEquals(listOf(player, entity, entity2), added) - assertContentEquals(emptyList(), removed) - assertContentEquals(listOf(player, entity, entity2), area.entitiesInArea) - - val (added2, removed2) = area.updateEntitiesInArea() - assertContentEquals(emptyList(), added2) - assertContentEquals(emptyList(), removed2) - assertContentEquals(listOf(player, entity, entity2), area.entitiesInArea) - } - - } - -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/position/MultiAreaTest.kt b/src/test/kotlin/com/github/rushyverse/api/position/MultiAreaTest.kt deleted file mode 100644 index f09b8056..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/position/MultiAreaTest.kt +++ /dev/null @@ -1,247 +0,0 @@ -package com.github.rushyverse.api.position - -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify -import net.minestom.server.entity.Entity -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class MultiAreaTest { - - @Nested - inner class Instantiation { - - @Test - fun `should have no entities at the creation`() { - val area = MultiArea() - assertTrue { area.areas.isEmpty() } - assertTrue { area.entitiesInArea.isEmpty() } - } - - @Test - fun `should have the areas passed in the constructor`() { - val area1 = mockk>() - val area2 = mockk>() - val area = MultiArea(mutableSetOf(area1, area2)) - assertEquals(setOf(area1, area2), area.areas) - } - - } - - @Nested - inner class AddArea { - - @Test - fun `should add an area`() { - val area = MultiArea() - val area2 = mockk>() - assertTrue { area.addArea(area2) } - assertEquals(1, area.areas.size) - } - - @Test - fun `should not add an area twice`() { - val area = MultiArea() - val area2 = mockk>() - assertTrue { area.addArea(area2) } - assertFalse { area.addArea(area2) } - assertEquals(1, area.areas.size) - } - - } - - @Nested - inner class RemoveArea { - - @Test - fun `should remove an area`() { - val area = MultiArea() - val area2 = mockk>() - assertTrue { area.addArea(area2) } - assertTrue { area.removeArea(area2) } - assertEquals(0, area.areas.size) - } - - @Test - fun `should not remove an area if it is not in the list`() { - val area = MultiArea() - val area2 = mockk>() - assertFalse { area.removeArea(area2) } - assertEquals(0, area.areas.size) - } - - } - - @Nested - inner class RemoveAllAreas { - - @Test - fun `should do nothing if there are no areas`() { - val area = MultiArea() - area.removeAllAreas() - assertEquals(0, area.areas.size) - } - - @Test - fun `should remove all areas`() { - val area = MultiArea() - val area2 = mockk>() - val area3 = mockk>() - assertTrue { area.addArea(area2) } - assertTrue { area.addArea(area3) } - area.removeAllAreas() - assertEquals(0, area.areas.size) - } - - } - - @Nested - inner class UpdateEntitiesInArea { - - @Test - fun `should call updateEntitiesInArea on all areas`() { - val area = MultiArea() - val area2 = mockk>() { - every { updateEntitiesInArea() } returns Pair(emptyList(), emptyList()) - every { entitiesInArea } returns emptySet() - } - val area3 = mockk>() { - every { updateEntitiesInArea() } returns Pair(emptyList(), emptyList()) - every { entitiesInArea } returns emptySet() - } - - area.addArea(area2) - area.addArea(area3) - area.updateEntitiesInArea() - - verify(exactly = 1) { area2.updateEntitiesInArea() } - verify(exactly = 1) { area2.entitiesInArea } - verify(exactly = 1) { area3.updateEntitiesInArea() } - verify(exactly = 1) { area3.entitiesInArea } - } - - @Test - fun `should register only one times an entity if in several areas`() { - val area = MultiArea() - val entity1 = mockk() - val entity2 = mockk() - - val area2 = mockk>() { - every { updateEntitiesInArea() } returns Pair(listOf(entity1, entity2), emptyList()) - every { entitiesInArea } returns setOf(entity1, entity2) - } - val area3 = mockk>() { - every { updateEntitiesInArea() } returns Pair(listOf(entity1), emptyList()) - every { entitiesInArea } returns setOf(entity1) - } - - area.addArea(area2) - area.addArea(area3) - val (added, removed) = area.updateEntitiesInArea() - - assertTrue { removed.isEmpty() } - - val expectedRegisteredEntities = setOf(entity1, entity2) - assertEquals(expectedRegisteredEntities, added) - assertEquals(expectedRegisteredEntities, area.entitiesInArea) - } - - @Test - fun `should remove an entity if it is not in any area`() { - val area = MultiArea() - val entity1 = mockk() - val entity2 = mockk() - - val area2 = mockk>() { - every { updateEntitiesInArea() } returns Pair(listOf(entity1, entity2), emptyList()) - every { entitiesInArea } returns setOf(entity1, entity2) - } - val area3 = mockk>() { - every { updateEntitiesInArea() } returns Pair(listOf(entity1), emptyList()) - every { entitiesInArea } returns setOf(entity1) - } - - area.addArea(area2) - area.addArea(area3) - area.updateEntitiesInArea() - - every { area2.updateEntitiesInArea() } returns Pair(emptyList(), listOf(entity1)) - every { area2.entitiesInArea } returns setOf(entity2) - every { area3.updateEntitiesInArea() } returns Pair(emptyList(), listOf(entity1)) - every { area3.entitiesInArea } returns setOf() - - val (added, removed) = area.updateEntitiesInArea() - - assertEquals(setOf(entity1), removed) - assertTrue { added.isEmpty() } - assertEquals(setOf(entity2), area.entitiesInArea) - } - - @Test - fun `should not remove an entity if he's at least in an area`() { - val area = MultiArea() - val entity1 = mockk() - val entity2 = mockk() - - val area2 = mockk>() { - every { updateEntitiesInArea() } returns Pair(listOf(entity1, entity2), emptyList()) - every { entitiesInArea } returns setOf(entity1, entity2) - } - val area3 = mockk>() { - every { updateEntitiesInArea() } returns Pair(listOf(entity1), emptyList()) - every { entitiesInArea } returns setOf(entity1) - } - - area.addArea(area2) - area.addArea(area3) - area.updateEntitiesInArea() - - every { area2.updateEntitiesInArea() } returns Pair(emptyList(), listOf(entity1)) - every { area2.entitiesInArea } returns setOf(entity2) - every { area3.updateEntitiesInArea() } returns Pair(emptyList(), emptyList()) - every { area3.entitiesInArea } returns setOf(entity1) - - val (added, removed) = area.updateEntitiesInArea() - - assertTrue { removed.isEmpty() } - assertTrue { added.isEmpty() } - assertEquals(setOf(entity1, entity2), area.entitiesInArea) - } - - @Test - fun `should not change entities in area if entities is always in areas`() { - val area = MultiArea() - val entity1 = mockk() - val entity2 = mockk() - - val area2 = mockk>() { - every { updateEntitiesInArea() } returns Pair(listOf(entity1, entity2), emptyList()) - every { entitiesInArea } returns setOf(entity1, entity2) - } - val area3 = mockk>() { - every { updateEntitiesInArea() } returns Pair(listOf(entity1, entity2), emptyList()) - every { entitiesInArea } returns setOf(entity1, entity2) - } - - area.addArea(area2) - area.addArea(area3) - area.updateEntitiesInArea() - - every { area2.updateEntitiesInArea() } returns Pair(emptyList(), emptyList()) - every { area2.entitiesInArea } returns setOf(entity1, entity2) - every { area3.updateEntitiesInArea() } returns Pair(emptyList(), emptyList()) - every { area3.entitiesInArea } returns setOf(entity1, entity2) - - val (added, removed) = area.updateEntitiesInArea() - - assertTrue { removed.isEmpty() } - assertTrue { added.isEmpty() } - assertEquals(setOf(entity1, entity2), area.entitiesInArea) - } - - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/position/SphereAreaTest.kt b/src/test/kotlin/com/github/rushyverse/api/position/SphereAreaTest.kt deleted file mode 100644 index 98dfe39f..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/position/SphereAreaTest.kt +++ /dev/null @@ -1,183 +0,0 @@ -package com.github.rushyverse.api.position - -import com.github.rushyverse.api.utils.randomPos -import com.github.rushyverse.api.utils.randomString -import io.mockk.every -import io.mockk.mockk -import net.minestom.server.coordinate.Pos -import net.minestom.server.entity.Entity -import net.minestom.server.entity.Player -import net.minestom.server.instance.Instance -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.assertThrows -import kotlin.test.Test -import kotlin.test.assertContentEquals -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -class SphereAreaTest { - - @Nested - inner class Instantiation { - - @Test - fun `should have no entities at the creation`() { - val area = SphereArea(mockk(), randomPos(), 0.0) - assertTrue { area.entitiesInArea.isEmpty() } - } - - @Test - fun `should throw an exception if the radius is set`() { - val area = SphereArea(mockk(), randomPos(), 0.0) - assertThrows { - area.radius = -1.0 - } - } - - @Test - fun `should set the radius without exception if value is zero or positive`() { - val area = SphereArea(mockk(), randomPos(), 0.0) - - area.radius = 0.0 - assertEquals(0.0, area.radius) - - area.radius = 1.0 - assertEquals(1.0, area.radius) - } - } - - @Nested - inner class UpdateEntitiesInArea { - - @Test - fun `should have filtered entities on type`() { - val player = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val player2 = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val entity = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val instance = mockk(randomString()) { - every { entities } returns setOf(player, player2, entity) - every { getNearbyEntities(any(), any()) } answers { - val pos = arg(0) - val range = arg(1) - entities.filter { it.position.distance(pos) <= range } - } - } - - val min = Pos(0.0, 0.0, 0.0) - val area = SphereArea(instance, min, 1.0) - val (added, removed) = area.updateEntitiesInArea() - - assertContentEquals(listOf(player, player2), added) - assertContentEquals(emptyList(), removed) - assertContentEquals(listOf(player, player2), area.entitiesInArea) - } - - @Test - fun `should have entities in the area`() { - val player = mockk(randomString()) { - every { position } returns Pos(1.0, 2.0, 4.0) - } - val entity = mockk(randomString()) { - every { position } returns Pos(2.0, 2.0, 4.0) - } - val entity2 = mockk(randomString()) { - every { position } returns Pos(5.0, 5.0, 5.0) - } - val instance = mockk(randomString()) { - every { entities } returns setOf(player, entity, entity2) - every { getNearbyEntities(any(), any()) } answers { - val pos = arg(0) - val range = arg(1) - entities.filter { it.position.distance(pos) <= range } - } - } - - val min = Pos(0.0, 0.0, 0.0) - val area = SphereArea(instance, min, 5.0) - val (added, removed) = area.updateEntitiesInArea() - - assertContentEquals(listOf(player, entity), added) - assertContentEquals(emptyList(), removed) - assertContentEquals(listOf(player, entity), area.entitiesInArea) - } - - @Test - fun `should remove entities in the area`() { - val player = mockk(randomString()) { - every { position } returns Pos(1.0, 2.0, 4.0) - } - val entity = mockk(randomString()) { - every { position } returns Pos(2.0, 2.0, 4.0) - } - val entity2 = mockk(randomString()) { - every { position } returns Pos(5.0, 5.0, 5.0) - } - val instance = mockk(randomString()) { - every { entities } returns setOf(player, entity, entity2) - every { getNearbyEntities(any(), any()) } answers { - val pos = arg(0) - val range = arg(1) - entities.filter { it.position.distance(pos) <= range } - } - } - - val min = Pos(0.0, 0.0, 0.0) - val area = SphereArea(instance, min, 5.0) - val (added, removed) = area.updateEntitiesInArea() - - assertContentEquals(listOf(player, entity), added) - assertContentEquals(emptyList(), removed) - assertContentEquals(listOf(player, entity), area.entitiesInArea) - - every { player.position } returns Pos(10.0, 0.0, 0.0) - every { entity2.position } returns Pos(0.0, 5.0, 0.0) - - val (added2, removed2) = area.updateEntitiesInArea() - assertContentEquals(listOf(entity2), added2) - assertContentEquals(listOf(player), removed2) - assertContentEquals(listOf(entity, entity2), area.entitiesInArea) - } - - @Test - fun `should not change entities in area if entities is always in areas`() { - val player = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val entity = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val entity2 = mockk(randomString()) { - every { position } returns Pos(0.0, 0.0, 0.0) - } - val instance = mockk(randomString()) { - every { entities } returns setOf(player, entity, entity2) - every { getNearbyEntities(any(), any()) } answers { - val pos = arg(0) - val range = arg(1) - entities.filter { it.position.distance(pos) <= range } - } - } - - val min = Pos(0.0, 0.0, 0.0) - val area = SphereArea(instance, min, 5.0) - val (added, removed) = area.updateEntitiesInArea() - - assertContentEquals(listOf(player, entity, entity2), added) - assertContentEquals(emptyList(), removed) - assertContentEquals(listOf(player, entity, entity2), area.entitiesInArea) - - val (added2, removed2) = area.updateEntitiesInArea() - assertContentEquals(emptyList(), added2) - assertContentEquals(emptyList(), removed2) - assertContentEquals(listOf(player, entity, entity2), area.entitiesInArea) - } - - } - -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/serializer/PosSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/serializer/PosSerializerTest.kt deleted file mode 100644 index 661860ec..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/serializer/PosSerializerTest.kt +++ /dev/null @@ -1,212 +0,0 @@ -package com.github.rushyverse.api.serializer - -import kotlinx.serialization.SerializationException -import kotlinx.serialization.json.Json -import net.minestom.server.coordinate.Pos -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.assertThrows -import kotlin.test.Test -import kotlin.test.assertEquals - -class PosSerializerTest { - - @Nested - inner class Serialize { - - @Nested - inner class OnlyCoordinate { - - @Test - fun `with positive values`() { - assertSerialize(14.0, 2.0, 375.0) - } - - @Test - fun `with negative values`() { - assertSerialize(-58518.0, -7.0, -6828126.0) - } - - @Test - fun `with zero values`() { - assertSerialize(0.0, 0.0, 0.0) - } - - @Test - fun `with decimal values`() { - assertSerialize(0.5, 0.7, 0.6) - } - - @Test - fun `with decimal values and negative values`() { - assertSerialize(-0.5, -0.7, -0.6) - } - - @Test - fun `with mixed values`() { - assertSerialize(0.5, -0.7, 0.6) - } - - } - - @Nested - inner class WithRotation { - - @Test - fun `with positive values`() { - assertSerialize(14.0, 2.0, 375.0, 0.5f, 0.7f) - } - - @Test - fun `with negative values`() { - assertSerialize(-58518.0, -7.0, -6828126.0, -0.5f, -0.7f) - } - - @Test - fun `with zero values`() { - assertSerialize(0.0, 0.0, 0.0, 0.0f, 0.0f) - } - - @Test - fun `with decimal values`() { - assertSerialize(0.5, 0.7, 0.6, 0.5f, 0.7f) - } - - @Test - fun `with decimal values and negative values`() { - assertSerialize(-0.5, -0.7, -0.6, -0.5f, -0.7f) - } - - @Test - fun `with mixed values`() { - assertSerialize(0.5, -0.7, 0.6, 0.1f, -0.2f) - } - - } - - private fun assertSerialize(x: Double, y: Double, z: Double, yaw: Float = 0f, pitch: Float = 0f) { - val pos = Pos(x, y, z, yaw, pitch) - val json = Json.encodeToString(PosSerializer, pos) - assertEquals("{\"x\":$x,\"y\":$y,\"z\":$z,\"yaw\":$yaw,\"pitch\":$pitch}", json) - } - - } - - @Nested - inner class Deserialize { - - @Nested - inner class OnlyCoordinate { - - @Test - fun `with positive values`() { - assertDeserialize(14.0, 2.0, 375.0) - } - - @Test - fun `with negative values`() { - assertDeserialize(-58518.0, -7.0, -6828126.0) - } - - @Test - fun `with zero values`() { - assertDeserialize(0.0, 0.0, 0.0) - } - - @Test - fun `with decimal values`() { - assertDeserialize(0.5, 0.7, 0.6) - } - - @Test - fun `with decimal values and negative values`() { - assertDeserialize(-0.5, -0.7, -0.6) - } - - @Test - fun `with mixed values`() { - assertDeserialize(0.5, -0.7, 0.6) - } - - } - - @Nested - inner class WithRotation { - - @Test - fun `with positive values`() { - assertDeserialize(14.0, 2.0, 375.0, 0.5f, 0.7f) - } - - @Test - fun `with negative values`() { - assertDeserialize(-58518.0, -7.0, -6828126.0, -0.5f, -0.7f) - } - - @Test - fun `with zero values`() { - assertDeserialize(0.0, 0.0, 0.0, 0.0f, 0.0f) - } - - @Test - fun `with decimal values`() { - assertDeserialize(0.5, 0.7, 0.6, 0.5f, 0.7f) - } - - @Test - fun `with decimal values and negative values`() { - assertDeserialize(-0.5, -0.7, -0.6, -0.5f, -0.7f) - } - - @Test - fun `with mixed values`() { - assertDeserialize(0.5, -0.7, 0.6, 0.1f, -0.2f) - } - - } - - @Nested - inner class MissingField { - - @Test - fun `with missing x`() { - val json = "{\"y\":0.0,\"z\":0.0,\"yaw\":0.0,\"pitch\":0.0}" - val exception = assertThrows { Json.decodeFromString(PosSerializer, json) } - assertEquals("The field x is missing", exception.message) - } - - @Test - fun `with missing y`() { - val json = "{\"x\":0.0,\"z\":0.0,\"yaw\":0.0,\"pitch\":0.0}" - val exception = assertThrows { Json.decodeFromString(PosSerializer, json) } - assertEquals("The field y is missing", exception.message) - } - - @Test - fun `with missing z`() { - val json = "{\"x\":0.0,\"y\":0.0,\"yaw\":0.0,\"pitch\":0.0}" - val exception = assertThrows { Json.decodeFromString(PosSerializer, json) } - assertEquals("The field z is missing", exception.message) - } - - @Test - fun `with missing yaw`() { - val json = "{\"x\":1.0,\"y\":2.0,\"z\":3.0,\"pitch\":4.0}" - val pos = Json.decodeFromString(PosSerializer, json) - assertEquals(Pos(1.0, 2.0, 3.0, 0.0f, 4.0f), pos) - } - - @Test - fun `with missing pitch`() { - val json = "{\"x\":1.0,\"y\":2.0,\"z\":3.0,\"yaw\":4.0}" - val pos = Json.decodeFromString(PosSerializer, json) - assertEquals(Pos(1.0, 2.0, 3.0, 4.0f, 0.0f), pos) - } - } - - private fun assertDeserialize(x: Double, y: Double, z: Double, yaw: Float = 0f, pitch: Float = 0f) { - val json = "{\"x\":$x,\"y\":$y,\"z\":$z,\"yaw\":$yaw,\"pitch\":$pitch}" - val pos = Json.decodeFromString(PosSerializer, json) - assertEquals(Pos(x, y, z, yaw, pitch), pos) - } - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationsProviderTest.kt b/src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationsProviderTest.kt deleted file mode 100644 index afa06086..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationsProviderTest.kt +++ /dev/null @@ -1,204 +0,0 @@ -package com.github.rushyverse.api.translation - -import com.github.rushyverse.api.utils.randomString -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import java.util.* -import kotlin.test.BeforeTest -import kotlin.test.assertEquals - -private const val BUNDLE_NAME = "test_bundle" - -private const val SECOND_BUNDLE_NAME = "test_bundle_2" - -class ResourceBundleTranslationsProviderTest { - - private lateinit var provider: ResourceBundleTranslationsProvider - - @BeforeTest - fun onBefore() { - provider = ResourceBundleTranslationsProvider() - } - - @Nested - inner class RegisterResourceBundle { - - @Test - fun `should load a resource bundle`() { - val locale = SupportedLanguage.ENGLISH.locale - provider.registerResourceBundle(BUNDLE_NAME, locale, ResourceBundle::getBundle) - assertEquals("english_value_1", provider.get("test1", locale, BUNDLE_NAME)) - assertEquals("english_value_2", provider.get("test2", locale, BUNDLE_NAME)) - } - - @Test - fun `should load a resource bundle for all supported locales`() { - provider.registerResourceBundleForSupportedLocales(BUNDLE_NAME, ResourceBundle::getBundle) - SupportedLanguage.values().forEach { - val displayName = it.displayName.lowercase() - assertEquals("${displayName}_value_1", provider.get("test1", it.locale, BUNDLE_NAME)) - assertEquals("${displayName}_value_2", provider.get("test2", it.locale, BUNDLE_NAME)) - } - } - - @Test - fun `should load multiple resource bundles`() { - val locale = SupportedLanguage.ENGLISH.locale - provider.registerResourceBundle(BUNDLE_NAME, locale, ResourceBundle::getBundle) - provider.registerResourceBundle(SECOND_BUNDLE_NAME, locale, ResourceBundle::getBundle) - assertEquals("english_value_1", provider.get("test1", locale, BUNDLE_NAME)) - assertEquals("English value", provider.get("simple_value", locale, SECOND_BUNDLE_NAME)) - } - } - - @Nested - inner class GetValue { - - @Test - fun `should throw an exception if the bundle is not registered`() { - val locale = SupportedLanguage.ENGLISH.locale - val ex = assertThrows { - provider.get("test1", locale, BUNDLE_NAME) - } - assertEquals(BUNDLE_NAME, ex.bundleName) - assertEquals(locale, ex.locale) - } - - @Test - fun `should throw an exception if the key is not found`() { - provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - assertThrows { - provider.get(randomString(), SupportedLanguage.ENGLISH.locale, BUNDLE_NAME) - } - } - - @Test - fun `should return the value for the given key`() { - provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - assertEquals("english_value_1", provider.get("test1", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME)) - } - - @Test - fun `should return the default value if the value is not defined for language`() { - provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - assertEquals("default_value", provider.get("test_undefined", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME)) - } - } - - @Nested - inner class TranslateValue { - - @Test - fun `should return the value for the given key`() { - provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - assertEquals("english_value_1", provider.translate("test1", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME)) - } - - @Test - fun `should return the value for the given key with the given array arguments`() { - provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - assertEquals( - "english_value with arguments", provider.translate( - "test_args", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME, arrayOf( - "with arguments" - ) - ) - ) - } - - @Test - fun `should return the value for the given key with the given list arguments`() { - provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - assertEquals( - "english_value with arguments", provider.translate( - "test_args", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME, listOf( - "with arguments" - ) - ) - ) - } - - @Test - fun `should return the key if the bundle is not registered`() { - val locale = SupportedLanguage.ENGLISH.locale - val ex = assertThrows { - assertEquals("test1", provider.translate("test1", locale, BUNDLE_NAME)) - } - assertEquals(BUNDLE_NAME, ex.bundleName) - assertEquals(locale, ex.locale) - } - - @Test - fun `should return the key if the value is not defined for language`() { - provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - val key = randomString() - assertEquals(key, provider.translate(key, SupportedLanguage.ENGLISH.locale, BUNDLE_NAME)) - } - - @Test - fun `should return the key if the value is not defined for language with the given array arguments`() { - provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - val key = randomString() - assertEquals(key, provider.translate(key, SupportedLanguage.ENGLISH.locale, BUNDLE_NAME, arrayOf("test"))) - } - - @Test - fun `should return the key if the value is not defined for language with the given list arguments`() { - provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - val key = randomString() - assertEquals(key, provider.translate(key, SupportedLanguage.ENGLISH.locale, BUNDLE_NAME, listOf("test"))) - } - - @Test - fun `should return the default value if the value is not defined for language`() { - provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - assertEquals( - "default_value", - provider.translate("test_undefined", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME) - ) - } - - @Test - fun `should return the value with template for args if no replacement args are defined`() { - provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - assertEquals( - "english_value {0}", - provider.translate("test_args", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME) - ) - } - - @Test - fun `should return the value with plural syntax`() { - provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - assertEquals( - "Need 2 players.", - provider.translate("test_plural", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME, arrayOf(2)) - ) - } - - @Test - fun `should return the value with singular syntax`() { - provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - assertEquals( - "Need 1 player.", - provider.translate("test_plural", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME, arrayOf(1)) - ) - - assertEquals( - "Need 0 player.", - provider.translate("test_plural", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME, arrayOf(0)) - ) - } - - @Test - fun `should return the UTF-8 value`() { - provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.FRENCH.locale, ResourceBundle::getBundle) - assertEquals( - "français_value_1", - provider.translate("test1", SupportedLanguage.FRENCH.locale, BUNDLE_NAME) - ) - } - - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/utils/Asserts.kt b/src/test/kotlin/com/github/rushyverse/api/utils/Asserts.kt deleted file mode 100644 index 4d63b317..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/utils/Asserts.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.github.rushyverse.api.utils - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.job -import kotlin.coroutines.CoroutineContext -import kotlin.test.assertEquals - -fun assertCoroutineContextFromScope(scope: CoroutineScope, coroutineContext: CoroutineContext) { - assertEquals(scope.coroutineContext.job.key, coroutineContext.job.key) -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/utils/FileUtilsTest.kt b/src/test/kotlin/com/github/rushyverse/api/utils/FileUtilsTest.kt deleted file mode 100644 index b260b8a2..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/utils/FileUtilsTest.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.github.rushyverse.api.utils - -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.io.TempDir -import java.io.File -import kotlin.test.assertEquals - -class FileUtilsTest { - - @Test - fun `should get the current directory where is executed the program`(@TempDir tmpDirectory: File) { - assertEquals(File(System.getProperty("user.dir")), workingDirectory) - - val initCurrentDirectory = System.getProperty("user.dir") - - System.setProperty("user.dir", tmpDirectory.absolutePath) - assertEquals(tmpDirectory, workingDirectory) - - System.setProperty("user.dir", initCurrentDirectory) - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt b/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt deleted file mode 100644 index 5da473e5..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.github.rushyverse.api.utils - -import net.minestom.server.coordinate.Pos -import java.net.ServerSocket -import java.util.* -import kotlin.random.Random - -private val stringGenerator = generateSequence { UUID.randomUUID().toString() }.distinct().iterator() - -fun randomString() = stringGenerator.next() - -private val intGenerator = generateSequence { Random.nextInt() }.distinct().iterator() - -fun randomInt() = intGenerator.next() - -private val posGenerator = - generateSequence { Pos(Random.nextDouble(), Random.nextDouble(), Random.nextDouble()) }.distinct().iterator() - -fun randomPos() = posGenerator.next() - -fun getAvailablePort(): Int { - return ServerSocket(0).use { - it.localPort - } -} \ No newline at end of file From 2e06f68012f6f7a463622ff32af5c713eb07e426 Mon Sep 17 00:00:00 2001 From: Distractic <46402441+Distractic@users.noreply.github.com> Date: Fri, 19 Aug 2022 09:52:47 +0200 Subject: [PATCH 018/143] Initial commit --- LICENSE | 4 +++- README.md | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 5b6b5f31..46104bba 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,8 @@ MIT License -Copyright (c) 2022 Rushyverse +<<<<<<< HEAD +Copyright (c) 2023 Rushyverse +======= Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 21ef12ff..ad9e1b37 100644 --- a/README.md +++ b/README.md @@ -5,4 +5,4 @@ server. ## Usage -Check the [wiki](https://github.com/Rushyverse/api/wiki) to know how to use the project. \ No newline at end of file +Check the [wiki](https://github.com/Rushyverse/api/wiki) to know how to use the project. From d117e9db050712644213fe1764a92464a78ea977 Mon Sep 17 00:00:00 2001 From: Distractic Date: Fri, 19 Aug 2022 12:50:11 +0200 Subject: [PATCH 019/143] feat: Begin migration from gitlab to github --- .github/workflows/Check.yml | 8 +- .github/workflows/CheckDependabot.yml | 28 ++ .gitignore | 23 +- build.gradle.kts | 126 ++++---- gradle.properties | 20 +- gradle/wrapper/gradle-wrapper.jar | Bin 60756 -> 59821 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- settings.gradle.kts | 2 +- .../fr/distractic/bukkit/api/APIPlugin.kt | 8 + .../kotlin/fr/distractic/bukkit/api/Plugin.kt | 55 ++++ .../exception/SilentCancellationException.kt | 20 ++ .../bukkit/api/delegate/DelegatePlayer.kt | 22 ++ .../bukkit/api/delegate/DelegateWorld.kt | 22 ++ .../bukkit/api/extension/_CommandSender.kt | 34 +++ .../bukkit/api/extension/_Comparable.kt | 15 + .../bukkit/api/extension/_Component.kt | 82 ++++++ .../bukkit/api/extension/_CoroutineScope.kt | 58 ++++ .../bukkit/api/extension/_Duration.kt | 45 +++ .../distractic/bukkit/api/extension/_Event.kt | 17 ++ .../bukkit/api/extension/_ItemStack.kt | 269 ++++++++++++++++++ .../bukkit/api/extension/_JavaPlugin.kt | 38 +++ .../bukkit/api/extension/_Listener.kt | 90 ++++++ .../bukkit/api/extension/_Location.kt | 88 ++++++ .../bukkit/api/extension/_MerchantRecipe.kt | 51 ++++ .../api/extension/_PersistentDataHolder.kt | 17 ++ .../bukkit/api/extension/_Player.kt | 39 +++ .../bukkit/api/extension/_PlayerProfile.kt | 28 ++ .../bukkit/api/extension/_Runnable.kt | 43 +++ .../bukkit/api/extension/_String.kt | 139 +++++++++ .../bukkit/api/extension/_Villager.kt | 47 +++ .../distractic/bukkit/api/extension/_World.kt | 60 ++++ .../bukkit/api/item/CraftBuilder.kt | 203 +++++++++++++ .../exception/CraftResultMissingException.kt | 6 + .../bukkit/api/koin/CraftContext.kt | 141 +++++++++ .../bukkit/api/listener/PlayerListener.kt | 62 ++++ .../bukkit/api/listener/VillagerListener.kt | 28 ++ .../fr/distractic/bukkit/api/player/Client.kt | 24 ++ .../bukkit/api/player/ClientManager.kt | 97 +++++++ .../bukkit/api/player/ClientManagerImpl.kt | 77 +++++ .../exception/ClientAlreadyExistsException.kt | 6 + .../exception/ClientNotFoundException.kt | 6 + .../exception/PlayerNotFoundException.kt | 6 + .../player/exception/PlayerQuitException.kt | 9 + .../bukkit/api/schedule/AbstractScheduler.kt | 45 +++ .../bukkit/api/schedule/Scheduler.kt | 29 ++ .../bukkit/api/schedule/SchedulerTask.kt | 221 ++++++++++++++ .../bukkit/api/world/BlockPosition.kt | 9 + .../fr/distractic/bukkit/api/world/Cuboid.kt | 105 +++++++ .../exception/WorldDifferentException.kt | 9 + .../world/exception/WorldNotFoundException.kt | 6 + 50 files changed, 2488 insertions(+), 99 deletions(-) create mode 100644 .github/workflows/CheckDependabot.yml create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/APIPlugin.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/Plugin.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/coroutine/exception/SilentCancellationException.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/delegate/DelegatePlayer.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/delegate/DelegateWorld.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/extension/_CommandSender.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/extension/_Comparable.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/extension/_Component.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/extension/_CoroutineScope.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/extension/_Duration.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/extension/_Event.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/extension/_ItemStack.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/extension/_JavaPlugin.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/extension/_Listener.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/extension/_Location.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/extension/_MerchantRecipe.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/extension/_PersistentDataHolder.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/extension/_Player.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/extension/_PlayerProfile.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/extension/_Runnable.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/extension/_String.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/extension/_Villager.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/extension/_World.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/item/CraftBuilder.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/item/exception/CraftResultMissingException.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/koin/CraftContext.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/listener/PlayerListener.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/listener/VillagerListener.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/player/Client.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/player/ClientManager.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/player/ClientManagerImpl.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/player/exception/ClientAlreadyExistsException.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/player/exception/ClientNotFoundException.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/player/exception/PlayerNotFoundException.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/player/exception/PlayerQuitException.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/schedule/AbstractScheduler.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/schedule/Scheduler.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/schedule/SchedulerTask.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/world/BlockPosition.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/world/Cuboid.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/world/exception/WorldDifferentException.kt create mode 100644 src/main/kotlin/fr/distractic/bukkit/api/world/exception/WorldNotFoundException.kt diff --git a/.github/workflows/Check.yml b/.github/workflows/Check.yml index a0a411e6..dfa67b74 100644 --- a/.github/workflows/Check.yml +++ b/.github/workflows/Check.yml @@ -2,6 +2,8 @@ name: Check on: push: + branches-ignore: + - 'dependabot-**' paths-ignore: - '**.md' @@ -11,7 +13,7 @@ concurrency: jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - name: Checkout project uses: actions/checkout@v3 @@ -22,11 +24,11 @@ jobs: jdk: 17 - name: Build - uses: gradle/gradle-build-action@v2.4.0 + uses: gradle/gradle-build-action@v2.2.2 with: arguments: build -x test - name: Test - uses: gradle/gradle-build-action@v2.4.0 + uses: gradle/gradle-build-action@v2.2.2 with: arguments: test \ No newline at end of file diff --git a/.github/workflows/CheckDependabot.yml b/.github/workflows/CheckDependabot.yml new file mode 100644 index 00000000..6845362d --- /dev/null +++ b/.github/workflows/CheckDependabot.yml @@ -0,0 +1,28 @@ +name: Check-Dependabot + +on: + pull_request_target: + branches: + - 'dependabot-**' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout project + uses: actions/checkout@v3 + + - name: Initialization + uses: ./.github/actions/init + with: + jdk: 17 + + - name: Build + uses: gradle/gradle-build-action@v2.2.2 + with: + arguments: build -x test + + - name: Test + uses: gradle/gradle-build-action@v2.2.2 + with: + arguments: test \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3dcc4b43..98ada130 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ - # Created by https://www.toptal.com/developers/gitignore/api/intellij,eclipse,kotlin,gradle # Edit at https://www.toptal.com/developers/gitignore?templates=intellij,eclipse,kotlin,gradle @@ -103,14 +102,14 @@ local.properties # When using Gradle or Maven with auto-import, you should exclude module files, # since they will be recreated, and may cause churn. Uncomment if using # auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr +.idea/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr # CMake cmake-build-*/ @@ -227,8 +226,4 @@ gradle-app.setting # End of https://www.toptal.com/developers/gitignore/api/intellij,eclipse,kotlin,gradle -dokka/ -# In case of someone execute the server in IDE -/*.conf -/world/ -/extensions/ \ No newline at end of file +dokka \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index d726d5b9..a8027719 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,68 +1,58 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - plugins { - kotlin("jvm") version "1.8.10" - kotlin("plugin.serialization") version "1.8.10" - id("org.jetbrains.dokka") version "1.8.10" - `java-library` + kotlin("jvm") version "1.7.10" + kotlin("plugin.serialization") version "1.7.10" + id("org.jetbrains.dokka") version "1.7.10" + id("com.github.johnrengelman.shadow") version "7.1.2" + id("net.researchgate.release") version "3.0.0" `maven-publish` } repositories { mavenCentral() - maven("https://jitpack.io") - maven("https://repo.papermc.io/repository/maven-public/") - maven("https://repo.codemc.org/repository/maven-public/") - maven("https://libraries.minecraft.net") + maven { + url = uri("https://repo.papermc.io/repository/maven-public/") + } } dependencies { - val paperVersion = "1.19.4-R0.1-SNAPSHOT" - val commandApiVersion = "8.8.0" - val loggingVersion = "3.0.5" - val mockkVersion = "1.13.4" - val coroutinesVersion = "1.6.4" - val kotlinSerializationVersion = "1.5.0" - val commonsNetVersion = "3.9.0" - val icu4jVersion = "72.1" - val minimessageVersion = "4.13.0" - - api(kotlin("stdlib")) - api(kotlin("reflect")) - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") - api("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutinesVersion") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlinSerializationVersion") - api("org.jetbrains.kotlinx:kotlinx-serialization-hocon:$kotlinSerializationVersion") - - api("io.papermc.paper:paper-api:$paperVersion") - api("dev.jorel:commandapi-kotlin:$commandApiVersion") - api("dev.jorel:commandapi-shade:$commandApiVersion") - api("com.mojang:brigadier:1.0.18") - - api("commons-net:commons-net:$commonsNetVersion") - api("com.ibm.icu:icu4j:$icu4jVersion") - api("net.kyori:adventure-text-minimessage:$minimessageVersion") - - // Logging information - api("io.github.microutils:kotlin-logging:$loggingVersion") + implementation(kotlin("stdlib")) + implementation(kotlin("reflect")) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + implementation("io.github.microutils:kotlin-logging:2.1.21") + + implementation("io.insert-koin:koin-core:3.2.0") + implementation("io.insert-koin:koin-logger-slf4j:3.2.0") + + implementation("com.github.shynixn.mccoroutine:mccoroutine-bukkit-api:2.2.0") + implementation("com.github.shynixn.mccoroutine:mccoroutine-bukkit-core:2.2.0") + + compileOnly("io.papermc.paper:paper-api:1.19-R0.1-SNAPSHOT") testImplementation(kotlin("test-junit5")) - testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion") - testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion") - testImplementation("io.mockk:mockk:$mockkVersion") + testImplementation("org.junit.jupiter:junit-jupiter-params:5.8.2") + testImplementation("io.insert-koin:koin-test:3.2.0") { + exclude("org.jetbrains.kotlin", "kotlin-test-junit") + } + testImplementation("io.mockk:mockk:1.12.2") + testImplementation("org.slf4j:slf4j-api:2.0.0-alpha6") + testImplementation("org.slf4j:slf4j-simple:2.0.0-alpha6") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") } kotlin { explicitApi = org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode.Strict + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } sourceSets { all { languageSettings { optIn("kotlin.RequiresOptIn") optIn("kotlin.ExperimentalStdlibApi") - optIn("kotlinx.serialization.ExperimentalSerializationApi") optIn("kotlin.contracts.ExperimentalContracts") - optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") } } } @@ -71,56 +61,47 @@ kotlin { val dokkaOutputDir = "${rootProject.projectDir}/dokka" tasks { - withType { - kotlinOptions.jvmTarget = JavaVersion.VERSION_17.toString() - } - test { useJUnitPlatform() } - clean { - delete(dokkaOutputDir) + build { + dependsOn(shadowJar) } - val deleteDokkaOutputDir by register("deleteDokkaOutputDirectory") { - group = "documentation" + clean { delete(dokkaOutputDir) } dokkaHtml.configure { - dependsOn(deleteDokkaOutputDir) + dependsOn(clean) outputDirectory.set(file(dokkaOutputDir)) } + + shadowJar { + archiveFileName.set("${project.name}.jar") + } } -val sourcesJar by tasks.registering(Jar::class) { - group = "build" - archiveClassifier.set("sources") - from(sourceSets.main.get().allSource) +val deleteDokkaOutputDir by tasks.register("deleteDokkaOutputDirectory") { + delete(dokkaOutputDir) } val javadocJar = tasks.register("javadocJar") { - group = "documentation" - dependsOn(tasks.dokkaHtml) + dependsOn(deleteDokkaOutputDir, tasks.dokkaHtml) archiveClassifier.set("javadoc") from(dokkaOutputDir) } publishing { - val projectName = project.name - publications { - val projectOrganizationPath = "Rushyverse/$projectName" + val projectOrganizationPath = "Distractic/${project.name}" val projectGitUrl = "https://github.com/$projectOrganizationPath" - create(projectName) { - from(components["kotlin"]) - artifact(sourcesJar.get()) - artifact(javadocJar.get()) - + create(project.name) { + artifact(javadocJar) pom { - name.set(projectName) + name.set(project.name) description.set(project.description) url.set(projectGitUrl) @@ -136,16 +117,11 @@ publishing { licenses { license { name.set("MIT") - url.set("https://mit-license.org") + url.set("https://mit-license.org/") } } developers { - developer { - name.set("Quentixx") - email.set("Quentixx@outlook.fr") - url.set("https://github.com/Quentixx") - } developer { name.set("Distractic") email.set("Distractic@outlook.fr") @@ -166,3 +142,7 @@ publishing { } } } + +release { + tagTemplate.set("v${version}") +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 650dbfe7..44dc6b35 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,14 @@ -kotlin.code.style=official -org.gradle.parallel=true -kotlin.incremental=true -group=com.github.Rushyverse -version=1.4.1 -description=Library to create Minestom server for Rushyverse +group=io.github.distractic +version=1.0.0 +description= + +org.gradle.parallel = true +kotlin.incremental = true + +paperVersion=1.19-R0.1-SNAPSHOT +viaVersionApiVersion=4.4.1 +rushyCoreVersion=1.0.0 +dataServiceVersion=9c936e59b8 +lombokVersion=1.18.24 +jupiterVersion=5.9.0 +mockitoVersion=4.7.0 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 249e5832f090a2944b7473328c07c9755baa3196..41d9927a4d4fb3f96a785543079b8df6723c946b 100644 GIT binary patch delta 9308 zcmY*TUd}iF{`GO1dV%zWK~?sM9OM(= zVK9&y4F^w1WFW{$qi|xQk0F`@HG8oLI5|5$j~ci9xTMT69v5KS-Yym--raU5kn2#C z<~5q^Bf0rTXVhctG2%&MG(cUGaz(gC(rcG~>qgO$W6>!#NOVQJ;pIYe-lLy(S=HgI zPh;lkL$l+FfMHItHnw_^bj8}CKM19t(C_2vSrhX2$K@-gFlH};#C?1;kk&U1L%4S~ zR^h%h+O1WE7DI$~dly?-_C7>(!E`~#REJ~Xa7lyrB$T!`&qYV5QreAa^aKr%toUJR zPWh)J3iD`(P6BI5k$oE$us#%!4$>`iH2p-88?WV0M$-K)JDibvA4 zpef%_*txN$Ei3=Lt(BBxZ&mhl|mUz-z*OD1=r9nfN zc5vOMFWpi>K=!$6f{eb?5Ru4M3o;t9xLpry|C%j~`@$f)OFB5+xo8XM8g&US@UU-sB|dAoc20y(F@=-2Ggp_`SWjEb#>IG^@j zuQK}e^>So#W2%|-)~K!+)wdU#6l>w5wnZt2pRL5Dz#~N`*UyC9tYechBTc2`@(OI# zNvcE*+zZZjU-H`QOITK^tZwOyLo)ZCLk>>Wm+flMsr5X{A<|m`Y281n?8H_2Fkz5}X?i%Rfm5s+n`J zDB&->=U+LtOIJ|jdYXjQWSQZFEs>Rm{`knop4Sq)(}O_@gk{14y51)iOcGQ5J=b#e z2Yx^6^*F^F7q_m-AGFFgx5uqyw6_4w?yKCJKDGGprWyekr;X(!4CnM5_5?KgN=3qCm03 z##6k%kIU5%g!cCL(+aK>`Wd;dZ4h$h_jb7n?nqx5&o9cUJfr%h#m4+Bh)>HodKcDcsXDXwzJ3jR(sSFqWV(OKHC*cV8;;&bH=ZI0YbW3PgIHwTjiWy z?2MXWO2u0RAEEq(zv9e%Rsz|0(OKB?_3*kkXwHxEuazIZ7=JhaNV*P~hv57q55LoebmJpfHXA@yuS{Esg+ z*C}0V-`x^=0nOa@SPUJek>td~tJ{U1T&m)~`FLp*4DF77S^{|0g%|JIqd-=5)p6a` zpJOsEkKT(FPS@t^80V!I-YJbLE@{5KmVXjEq{QbCnir%}3 zB)-J379=wrBNK6rbUL7Mh^tVmQYn-BJJP=n?P&m-7)P#OZjQoK0{5?}XqJScV6>QX zPR>G{xvU_P;q!;S9Y7*07=Z!=wxIUorMQP(m?te~6&Z0PXQ@I=EYhD*XomZ^z;`Os z4>Uh4)Cg2_##mUa>i1Dxi+R~g#!!i{?SMj%9rfaBPlWj_Yk)lCV--e^&3INB>I?lu z9YXCY5(9U`3o?w2Xa5ErMbl5+pDVpu8v+KJzI9{KFk1H?(1`_W>Cu903Hg81vEX32l{nP2vROa1Fi!Wou0+ZX7Rp`g;B$*Ni3MC-vZ`f zFTi7}c+D)!4hz6NH2e%%t_;tkA0nfkmhLtRW%){TpIqD_ev>}#mVc)<$-1GKO_oK8 zy$CF^aV#x7>F4-J;P@tqWKG0|D1+7h+{ZHU5OVjh>#aa8+V;6BQ)8L5k9t`>)>7zr zfIlv77^`Fvm<)_+^z@ac%D&hnlUAFt8!x=jdaUo{)M9Ar;Tz5Dcd_|~Hl6CaRnK3R zYn${wZe8_BZ0l0c%qbP}>($jsNDay>8+JG@F!uV4F;#zGsBP0f$f3HqEHDz_sCr^q z1;1}7KJ9&`AX2Qdav1(nNzz+GPdEk5K3;hGXe{Hq13{)c zZy%fFEEH#nlJoG{f*M^#8yXuW%!9svN8ry-Vi7AOFnN~r&D`%6d#lvMXBgZkX^vFj z;tkent^62jUr$Cc^@y31Lka6hS>F?1tE8JW$iXO*n9CQMk}D*At3U(-W1E~z>tG?> z5f`5R5LbrhRNR8kv&5d9SL7ke2a*Xr)Qp#75 z6?-p035n2<7hK;sb>t9GAwG4{9v~iEIG>}7B5zcCgZhu$M0-z8?eUO^E?g)md^XT_ z2^~-u$yak>LBy(=*GsTj6p<>b5PO&un@5hGCxpBQlOB3DpsItKZRC*oXq-r{u}Wb; z&ko>#fbnl2Z;o@KqS-d6DTeCG?m1 z&E>p}SEc*)SD&QjZbs!Csjx~0+$@ekuzV_wAalnQvX3a^n~3ui)|rDO+9HW|JPEeBGP4 z)?zcZ<8qv47`EWA*_X~H^vr(lP|f%=%cWFM;u)OFHruKT<~?>5Y8l?56>&;=WdZU# zZEK4-C8s-3zPMA^&y~e*9z)!ZJghr3N^pJa2A$??Xqx-BR*TytGYor&l8Q+^^r%Yq02xay^f#;;wO6K7G!v>wRd6531WnDI~h$PN( z+4#08uX?r&zVKsQ;?5eBX=FxsXaGyH4Gth4a&L|{8LnNCHFr1M{KjJ!BfBS_aiy-E zxtmNcXq3}WTwQ7Dq-9YS5o758sT(5b`Sg-NcH>M9OH1oW6&sZ@|GYk|cJI`vm zO<$~q!3_$&GfWetudRc*mp8)M)q7DEY-#@8w=ItkApfq3sa)*GRqofuL7)dafznKf zLuembr#8gm*lIqKH)KMxSDqbik*B(1bFt%3Vv|ypehXLCa&wc7#u!cJNlUfWs8iQ` z$66(F=1fkxwg745-8_eqV>nWGY3DjB9gE23$R5g&w|C{|xvT@7j*@aZNB199scGchI7pINb5iyqYn)O=yJJX)Ca3&Ca+{n<=1w|(|f0)h<9gs$pVSV<<9Og-V z8ki@nKwE)x)^wmHBMk?mpMT=g{S#^8W|>&rI#Ceh;9za}io0k@0JxiCqi-jHlxbt3 zjJA?RihhRvhk6%G5-D{ePh1jare*fQS<328P-DcVAxPTrw=n6k?C6EV75f}cnBRPT zMYDqqKu(ND&aOtc!QRV`vzJSVxx8i~WB#5Ml{b#eQqNnSi7l-bS-`ITW<^zyYQA(b zbj4SuRK>q9o`_v%+C=S?h>2e4!66Ij(P5{7Uz$3u6YJJC$W%EoBa{-(=tQ|y1vov%ZkXVOV z##_UVg4V^4ne#4~<-1DkJqkKqgT+E_=&4Ue&eQ-JC+gi?7G@d6= zximz{zE)WW{b@QCJ!7l&N5x=dXS?$5RBU-VvN4Uec-GHK&jPa&P2z+qDdLhIB+HU) zu0CW&uLvE^4I5xtK-$+oe|58)7m6*PO%Xt<+-XEA%jG_BEachkF3e@pn?tl!`8lOF zbi2QOuNXX)YT*MCYflILO{VZ*9GiC%R4FO20zMK?p+&aCMm2oeMK7(aW=UDzr=AO0 z$5mJ%=qRsR8rZ>_YsL+vi{3*J_9Kzq(;ZwRj+4_f0-*wbkSMPWahX#Fj_a8BnrhJ6 zo^ZZ?Vah1@&6#r=JkuaYDBdp;J3@ii+CHM&@9*er&#P}$@wI$bfrH)&c!*|nkvhf%^*Y6b%dKz%QBSIo@U z{?V^qEs4`q<8@n+u8YiB^sc@6g>TncG<|GsmC3egwE6aO=EwLr~3-2 zNr`+)`i+-83?|1Xy0^8ps&pb}YT?w1eWVnC9Ps1=KM;Rw)bH6O!7Did1NwpnqVPZc z*%Qo~qkDL>@^<^fmIBtx$WUWQiNtAB2x-LO^BB=|w~-zTnJNEdm1Ou(?8PF&U88X@ z#8rdaTd||)dG^uJw~N_-%!XNbuAyh4`>Shea=pSj0TqP+w4!`nxsmVSv02kb`DBr% zyX=e>5IJ3JYPtdbCHvKMdhXUO_*E9jc_?se7%VJF#&ZaBD;7+eFN3x+hER7!u&`Wz z7zMvBPR4y`*$a250KYjFhAKS%*XG&c;R-kS0wNY1=836wL6q02mqx;IPcH(6ThA@2 zXKQF|9H>6AW$KUF#^A%l6y5{fel77_+cR_zZ0(7=6bmNXABv}R!B-{(E^O6Y?ZS)n zs1QEmh_Fm7p}oRyT3zxUNr4UV8NGs+2b8|4shO$OGFj3D&7_e?#yDi=TTe%$2QbG5 zk<;q7aQ;p!M-Osm{vFdmXZ@!z9uWh!;*%>(vTRggufuUGP9Hols@vhx z73pn$3u2;vzRvnXuT&$Os7J@6y12*j!{ix%3B4YU1466ItmJs0NsU(4ZYRYh7wEA6q{b*Hs6@k~ zi7Yq@Ax!et0cUMTvk7P%ym){MHpcliHEI~e3HP0NV=}7;xFv#IC?a<=`>~j_sk{e> z7vg-tK*p83HZ0=QK@ zRIHo^r{D8&Ms-^WZp+6US_Quqjh$Q66W^1}=Uz&XJ8AQE9&2}P zY|FXZzZ|0IiaBd2qdt6dIjQr(ZMIOU%NG1F&fu6Po9m^?BvLhI6T0R!H2d8;U(&p2 zYA|MFscMqcO(ye~Jp?F;0>Ke+5hzVr?aBNe>GsGgr$XrpS9uajN2kNQ3o$V5rp0T( z0$6TJC;3)26SNG#XcX7l^MKTn$ga?6r4Jzfb%ZgA(Zbwit0$kY=avSnI$@Gk%+^pu zS5mHrcRS8LFPC*uVWH4DDD1pY$H8N>X?KIJZuZ2SvTqc5Nr0GHdD8TCJcd$zIhOdC zZX0ErnsozQh;t^==4zTfrZO421AL?)O)l#GSxU#|LTTg4#&yeK=^w#;q63!Nv~1(@ zs^-RNRuF&qgcr+bIzc@7$h9L;_yjdifE*$j0Q&Np=1AuHL--zdkv@}`1 zo~LlDl_YAq*z?vmr4M`GjDkl9?p|-tl(DtX76oZv25_DtZutLS9Ez!5~p?th@4 zyc_uax4W#<(#)LMkvo)yp|5tKsC2=p#6PyhpH|449T<9Zdk|%CAb5cw?fhvQtBO&7 zpQ9$24yLqPHP;$N&fe2wm%8qdctwIna<3SwGtQA3{C77s%CW%LYxtK(SBGustL0<( zu~U9r0UOkr(c{OJxZS0Ntu3+cJlF7R`7k-Bsa&q?9Ae5{{|o~?cM+T7{lB1^#vT8R z?>c9fNWey`1dKDY%F3d2O*8^qYhjlB8*7HMKE<*=(A`{>=1%s1}Pm&#_t1xy!FkPk@%SMEka2@*= zxDuM|vJJ5s+xgDls{>*o!7eOcs|xuVBPWX&+y5vEiADK%hi`#Dbd>;;Pbk2H4*-X&R?_-6ZEutSd8hC+sSjhIo z;D(j4P;2EVpEj#UF7IjM6PC+X$C5T&=nL`*!*hm9U)#O?>wqOgC>jXKN3Slk_yaQX zLf|4D8T4k|wHW`;#ZQVocNF|3izi0sOqXzi7@KlYC3CXBG`94wD;tMI1bj|8Vm zY}9`VI9!plSfhAal$M_HlaYOVNU?9Z#0<$o?lXXbX3O(l_?f)i3_~r+GcO-x#+x^X zfsZl0>Rj2iP1rsT;+b;Mr? z4Vu&O)Q5ru4j;qaSP5gA{az@XTS1NpT0d9Xhl_FkkRpcEGA0(QQ~YMh#&zwDUkNzm z6cgkdgl9W{iL6ArJ1TQHqnQ^SQ1WGu?FT|93$Ba}mPCH~!$3}0Y0g zcoG%bdTd$bmBx9Y<`Jc+=Cp4}c@EUfjiz;Rcz101p z=?#i$wo>gBE9|szaZMt-d4nUIhBnYRuBVyx+p?5#aZQgUe(!ah`J#l1$%bl5avL27 zU2~@V`3Ic&!?FhDX@Cw!R4%xtWark#p8DLT)HCZ?VJxf^yr@AD*!ERK3#L$E^*Yr? zzN&uF9Roh4rP+r`Z#7U$tzl6>k!b~HgM$C<_crP=vC>6=q{j?(I}!9>g3rJU(&){o z`R^E*9%+kEa8H_fkD9VT7(Fks&Y-RcHaUJYf-|B+eMXMaRM;{FKRiTB>1(=Iij4k1(X__|WqAd-~t#2@UQ}Z&<1Th0azdXfoll!dd)6>1miA z!&=6sDJm=e$?L&06+Q3`D-HNSkK-3$3DdZMX-6Xjn;wd#9A{~ur!2NcX>(qY_oZL0~H7dnQ9sgLe!W>~2|RSW7|hWn<({Pg*xF$%B-!rKe^_R_vc z(LO!0agxxP;FWPV({8#lEv$&&GVakGus=@!3YVG`y^AO1m{2%Np;>HNA1e{=?ra1C}H zAwT0sbwG|!am;fl?*_t^^#yLDXZ*Nx)_FqueZi0c-G~omtpHW0Cu)mEJ`Z1X8brq$ z%vK##b~o*^b&Hz!hgrD=^6P8}aW40lhzMLB5T5*v`1QH?+L~-@CDi3+C@nRf2{7UE zyDIe{@LKw`Eu=Z%6<<_=#V|yxJIKiq_N?ZJ_v0$c)N4l07ZV_mIXG}glfBSPivOhw z-~+9GdckSpMBNR9eR`Y|9_)sXS+u_OiQ%!9rE(2AFjoxN8lk16Sb~^Sq6kRoEp3yD(mm`HsYIXcag_EAB8MHc}nahxVVUTts~U9P|f;7Ul$_` zStR4v&P4q_$KXOEni$lkxy8=9w8G&47VY0oDb^+jT+>ARe3NHUg~St`$RDxY)?;_F znqTujR&chZd2qHF7y8D$4&E3+e@J~!X3&BW4BF(Ebp#TEjrd+9SU!)j;qH+ZkL@AW z?J6Mj}v0_+D zH0qlbzCkHf|EZ`6c>5ig5NAFF%|La%M-}g(7&}Vx8K)qg30YD;H!S!??{;YivzrH0 z(M%2*b_S-)yh&Aiqai)GF^c!<1Xemj|13>dZ_M#)41SrP;OEMaRJ)bCeX*ZT7W`4Y zQ|8L@NHpD@Tf(5>1U(s5iW~Zdf7$@pAL`a3X@YUv1J>q-uJ_(Dy5nYTCUHC}1(dlI zt;5>DLcHh&jbysqt?G01MhXI3!8wgf){Hv}=0N|L$t8M#L7d6WscO8Om2|NBz2Ga^ zs86y%x$H18)~akOWD7@em7)ldlWgb?_sRN>-EcYQO_}aX@+b$dR{146>{kXWP4$nN{V0_+|3{Lt|8uX_fhKh~i{(x%cj*PU$i{PO(5$uA? zQzO>a6oPj-TUk&{zq?JD2MNb6Mf~V3g$ra+PB;ujLJ2JM(a7N*b`y{MX--!fAd}5C zF$D_b8S;+Np(!cW)(hnv5b@@|EMt*RLKF*wy>ykFhEhlPN~n_Bj>LT9B^_yj>z#fx z3JuE4H&?Cc!;G@}E*3k`HK#8ag`yE3Z1)5JUlSua%qkF zkTu|<9{w9OSi$qr)WD#7EzITnch=xnR63E*d~WGvi*Co9BBE?ETHud;!Z)7&wz+l6 zuKODYG1>I1U#a%&(GNJ`AqRfg=H!BtSl+_;CEeufF-#+*2EMMz-22@>18=8PH{PHd z);mN=aR0MPF>eutLiS#-AOX>#2%+pTGEOj!j4L(m0~&xR=0+g#HNpno6@veLhJp}e zyNVC$a>4;!9&iGvU_dj&xbKt@^t6r%f^)+}eV^suRTLP52+BVs0kOLwg6n`=NUv50E7My8XQUh?y%mW62OT1pMrKI3Q(r`7vU&@93=G~A?b(^pvC-8x=bSk zZ60BQR96WB1Z@9Df(M1IQh+YrU8sEjB=Tc2;(zBn-pete*icZE|M&Uc+oHg`|1o`g zH~m+k=D$o);{Rs)b<9Zo|9_Z6L6QHLNki(N>Dw^^i1LITprZeeqIaT#+)fw)PlllU zldphHC)t!0Gf(i9zgVm>`*TbmITF zH1FZ4{wrjRCx{t^26VK_2srZuWuY*EMAsMrJYFFCH35Ky7bq8<0K|ey2wHnrFMZyr z&^yEgX{{3i@&iE5>xKZ{Ads36G3a!i50D!C4?^~cLB<<|fc1!XN(HJRM)H^21sEs%vv+Mu0h*HkLHaEffMwc0n6)JhNXY#M5w@iO@dfXY z0c6dM2a4Hd1SA*#qYj@jK}uVgAZdaBj8t6uuhUNe>)ne9vfd#C6qLV9+@Q7{MnF#0 zJ7fd-ivG_~u3bVvOzpcw1u~ZSp8-kl(sunnX>L~*K-ByWDM2E8>;Si6kn^58AZQxI xVa^It*?521mj4+UJO?7%w*+`EfEcU=@KhDx-s^WzP+ae~{CgHDE&XryzW}Nww%-5% delta 10197 zcmaKS1ymhDwk=#NxVyW%y9U<)A-Dv)xI0|j{UX8L-JRg>5ZnnKAh;%chM6~S-g^K4 z>eZ{yK4;gd>gwvXs=Id8Jk-J}R4pT911;+{Jp9@aiz6!p1Oz9z&_kGLA%J5%3Ih@0 zQ|U}%$)3u|G`jIfPzMVfcWs?jV2BO^*3+q2><~>3j+Z`^Z%=;19VWg0XndJ zwJ~;f4$;t6pBKaWn}UNO-wLCFHBd^1)^v%$P)fJk1PbK5<;Z1K&>k~MUod6d%@Bq9 z>(44uiaK&sdhwTTxFJvC$JDnl;f}*Q-^01T508(8{+!WyquuyB7R!d!J)8Ni0p!cV6$CHsLLy6}7C zYv_$eD;)@L)tLj0GkGpBoa727hs%wH$>EhfuFy{_8Q8@1HI%ZAjlpX$ob{=%g6`Ox zLzM!d^zy`VV1dT9U9(^}YvlTO9Bf8v^wMK37`4wFNFzW?HWDY(U(k6@tp(crHD)X5>8S-# zW1qgdaZa*Sh6i%60e1+hty}34dD%vKgb?QmQiZ=-j+isA4={V_*R$oGN#j|#ia@n6 zuZx4e2Xx?^lUwYFn2&Tmbx0qA3Z8;y+zKoeQu;~k~FZGy!FU_TFxYd!Ck;5QvMx9gj5fI2@BLNp~Ps@ zf@k<&Q2GS5Ia9?_D?v~$I%_CLA4x~eiKIZ>9w^c#r|vB?wXxZ(vXd*vH(Fd%Me8p( z=_0)k=iRh%8i`FYRF>E97uOFTBfajv{IOz(7CU zv0Gd84+o&ciHlVtY)wn6yhZTQQO*4Mvc#dxa>h}82mEKKy7arOqU$enb9sgh#E=Lq zU;_RVm{)30{bw+|056%jMVcZRGEBSJ+JZ@jH#~DvaDQm92^TyUq=bY*+AkEakpK>8 zB{)CkK48&nE5AzTqT;WysOG|!y}5fshxR8Ek(^H6i>|Fd&wu?c&Q@N9ZrJ=?ABHI! z`*z8D`w=~AJ!P-9M=T}f`;76$qZRllB&8#9WgbuO$P7lVqdX1=g*t=7z6!0AQ^ux_ z9rcfUv^t}o_l-ZE+TqvqFsA*~W<^78!k;~!i8(eS+(+@u8FxK+Q7;mHZ<1}|4m<}vh@p`t%|@eM_J(P% zI>M7C)Ir{l|J;$G_EGGEhbP4?6{sYzMqBv+x95N&YWFH6UcE@b}B?q)G*4<4mR@sy1#vPnLMK51tb#ED(8TA1nE zYfhK7bo1!R5WJF$5Y?zG21)6+_(_5oSX9sGIW;(O&S?Rh(nydNQYzKjjJ54aDJ-1F zrJ=np8LsN?%?Rt7f~3aAX!2E{`fh_pb?2(;HOB3W+I*~A>W%iY+v45+^e$cE10fA} zXPvw9=Bd+(;+!rl)pkYj0HGB}+3Z!Mr;zr%gz~c-hFMv8b2VRE2R$8V=_XE zq$3=|Yg05(fmwrJ)QK2ptB4no`Y8Dg_vK2QDc6-6sXRQ5k78-+cPi-fH}vpgs|Ive zE=m*XNVs?EWgiNI!5AcD*3QMW)R`EqT!f0e1%hERO&?AT7HWnSf5@#AR{OGuXG3Zb zCnVWg7h|61lGV3k+>L<#d>)InG>ETn1DbOHCfztqzQ_fBiaUt@q6VMy={Fe-w#~2- z0?*f|z$zgjI9>+JVICObBaK=pU}AEOd@q(8d?j7zQFD@=6t`|KmolTr2MfBI$;EGh zD%W0cA_d#V6Lb$us5yIG(|d>r-QleC4;%hEu5W9hyY zY#+ESY&v`8(&mC~?*|e5WEhC!YU2>m_}`K+q9)a(d$bsS<=YkyZGp}YA%TXw>@abA zS_poVPoN+?<6?DAuCNt&5SHV(hp56PJ})swwVFZFXM->F zc|0c8<$H_OV%DR|y7e+s$12@Ac8SUClPg8_O9sTUjpv%6Jsn5vsZCg>wL+db4c+{+ zsg<#wOuV4jeOq`veckdi-1`dz;gvL)bZeH|D*x=8UwRU5&8W1@l>3$)8WzET0%;1J zM3(X<7tKK&9~kWRI{&FmwY5Gg!b5f4kI_vSm)H1#>l6M+OiReDXC{kPy!`%Ecq-+3yZTk=<` zm)pE6xum5q0Qkd#iny0Q-S}@I0;mDhxf>sX)Oiv)FdsAMnpx%oe8OQ`m%Xeozdzx!C1rQR>m1c_}+J4x)K}k{G zo68;oGG&Ox7w^-m7{g4a7NJu-B|~M;oIH~~#`RyUNm##feZH;E?pf}nshmoiIY52n z%pc%lnU4Q#C=RUz)RU6}E_j4#)jh<&a%JyJj$Fufc#&COaxFHtl}zJUGNLBu3~_@1 zn9F^JO9);Duxo&i@>X(kbYga1i>6p1fca8FzQ0>((Lb-aPUbC*d~a03V$y;*RBY!R ziEJ2IF^FjrvO}0Uy{cMn%u<+P5U!UO>pm9#ZYL5i6|xSC+np7IH$GfXs&uI;y4as@ z&AzJh>(S2?3PKKgab3Z(`xbx(C#46XIvVcW8eG_DjT~}Yz_8PWZ`uf6^Xr=vkvL_` zqmvfgJL+Zc`;iq~iP?%@G7}~fal-zqxa0yNyHBJJ5M)9bI>7S_cg?Ya&p(I)C5Ef4 zZ>YAF6x|U=?ec?g*|f2g5Tw3PgxaM_bi_5Az9MO$;_Byw(2d}2%-|bg4ShdQ;)Z|M z4K|tFv)qx*kKGKoyh!DQY<{n&UmAChq@DJrQP>EY7g1JF(ih*D8wCVWyQ z5Jj^|-NVFSh5T0vd1>hUvPV6?=`90^_)t(L9)XOW7jeP45NyA2lzOn&QAPTl&d#6P zSv%36uaN(9i9WlpcH#}rmiP#=L0q(dfhdxvFVaOwM;pY;KvNQ9wMyUKs6{d}29DZQ z{H3&Sosr6)9Z+C>Q5)iHSW~gGoWGgK-0;k~&dyr-bA3O|3PCNzgC?UKS_B=^i8Ri^ zd_*_qI4B07Cayq|p4{`U_E_P=K`N_~{F|+-+`sCgcNxs`%X!$=(?l2aAW}0M=~COb zf19oe^iuAUuDEf)4tgv<=WRPpK@IjToNNC*#&Ykw!)aqWU4h#|U@(cG_=Qx+&xt~a zvCz~Ds3F71dsjNLkfM%TqdVNu=RNMOzh7?b+%hICbFlOAPphrYy>7D-e7{%o_kPFn z;T!?ilE-LcKM0P(GKMseEeW57Vs`=FF}(y@^pQl;rL3fHs8icmA+!6YJt&8 ztSF?%Un35qkv>drkks&BNTJv~xK?vD;aBkp7eIkDYqn+G0%;sT4FcwAoO+vke{8CO z0d76sgg$CannW5T#q`z~L4id)9BCKRU0A!Z-{HpXr)QJrd9@iJB+l32Ql)Z}*v(St zE)Vp=BB=DDB4Pr}B(UHNe31<@!6d{U?XDoxJ@S)9QM)2L%SA0x^~^fb=bdsBy!uh& zU?M_^kvnt%FZzm+>~bEH{2o?v&Iogs`1t-b+Ml`J!ZPS(46YQJKxWE81O$HE5w;** z|8zM%bp`M7J8)4;%DqH`wVTmM0V@D}xd%tRE3_6>ioMJxyi5Hkb>85muF81&EY!73ei zA3e<#ug||EZJ=1GLXNJ)A z791&ge#lF;GVX6IU?iw0jX^1bYaU?+x{zPlpyX6zijyn*nEdZ$fxxkl!a-~*P3bkf zPd*pzu~3GBYkR_>ET`5UM^>>zTV>5m>)f=az{d0sg6a8VzUtXy$ZS?h#Gk-CA?7)c zI%Vu9DN6XSDQn6;?n9`>l$q&>s?K)R8*OsmI+$L_m z_~E`}w694Z*`Xk3Ne=497Si~=RWRqCM?6=88smrxle#s*W znwhTRsMRmg?37GLJ-)%nDZA7r$YG849j8mJWir1bWBy& zZPneYojSbooC8U@tkO`bWx4%E5*;p#Q^1^S3lsfy7(6A{jL0`A__0vm?>xC%1y8_m z57FfWr^@YG2I1K7MGYuYd>JC}@sT2n^rkrY3w%~$J$Y~HSoOHn?zpR$ zjLj_bq@Yj8kd~DXHh30KVbz@K)0S;hPKm+S&-o%IG+@x@MEcrxW2KFh;z^4dJDZix zGRGe&lQD$p)0JVF4NRgGYuh0bYLy)BCy~sbS3^b3 zHixT<%-Vwbht|25T{3^Hk;qZ^3s!OOgljHs+EIf~C%=_>R5%vQI4mQR9qOXThMXlU zS|oSH>0PjnCakb*js2{ObN`}%HYsT6=%(xA| znpUtG_TJ08kHgm5l@G|t?4E3tG2fq?wNtIp*Vqrb{9@bo^~Rx7+J&OnayrX`LDcF~ zd@0m0ZJ#Z@=T>4kTa5e2FjI&5c(F7S{gnRPoGpu9eIqrtSvnT_tk$8T)r%YwZw!gK zj*k@cG)V&@t+mtDi37#>LhVGTfRA^p%x0d#_P|Mktz3*KOoLIqFm`~KGoDDD4OOxe z?}ag_c08u%vu=5Vx=~uoS8Q;}+R2~?Uh|m-+`-2kDo$d6T!nD*hc#dB(*R{LXV=zo z`PJP0V=O!@3l-bw+d`X6(=@fq=4O#ETa8M^fOvO4qja9o3e8ANc9$sI=A4$zUut~w z4+JryRkI{9qWxU1CCMM$@Aj=6)P+z?vqa=UCv_4XyVNoBD{Xb~Oi4cjjhm8fRD!*U z2)zaS;AI78^Wq+5mDInKiMz|z#K`2emQfNH*U;{9^{NqSMVoq?RSo43<8YpJM^+W$ zxy!A5>5Zl16Vi#?nAYywu3w_=KWnd3*QetocWt`3pK67>)ZVwnT3h zbPdD&MZkD?q=-N`MpCCwpM74L+Tr1aa)zJ)8G;(Pg51@U&5W>aNu9rA`bh{vgfE={ zdJ>aKc|2Ayw_bop+dK?Y5$q--WM*+$9&3Q9BBiwU8L<-`T6E?ZC`mT0b}%HR*LPK} z!MCd_Azd{36?Y_>yN{U1w5yrN8q`z(Vh^RnEF+;4b|2+~lfAvPT!`*{MPiDioiix8 zY*GdCwJ{S(5(HId*I%8XF=pHFz<9tAe;!D5$Z(iN#jzSql4sqX5!7Y?q4_%$lH zz8ehZuyl0K=E&gYhlfFWabnSiGty$>md|PpU1VfaC5~kskDnZX&Yu}?-h;OSav=8u z=e3Yq=mi$4A|sB-J00;1d{Sd1+!v0NtU((Nz2;PFFlC}V{@p&4wGcVhU&nI($RAS! zwXn7)?8~1J3*4+VccRSg5JS<(bBhBM&{ELMD4C_NTpvzboH!{Zr*%HP;{UqxI#g&7 zOAqPSW5Qus$8-xtTvD%h{Tw<2!XR(lU54LZG{)Cah*LZbpJkA=PMawg!O>X@&%+5XiyeIf91n2E*hl$k-Y(3iW*E}Mz-h~H~7S9I1I zR#-j`|Hk?$MqFhE4C@=n!hN*o5+M%NxRqP+aLxDdt=wS6rAu6ECK*;AB%Nyg0uyAv zO^DnbVZZo*|Ef{nsYN>cjZC$OHzR_*g%T#oF zCky9HJS;NCi=7(07tQXq?V8I&OA&kPlJ_dfSRdL2bRUt;tA3yKZRMHMXH&#W@$l%-{vQd7y@~i*^qnj^`Z{)V$6@l&!qP_y zg2oOd!Wit#)2A~w-eqw3*Mbe)U?N|q6sXw~E~&$!!@QYX4b@%;3=>)@Z#K^`8~Aki z+LYKJu~Y$;F5%_0aF9$MsbGS9Bz2~VUG@i@3Fi2q(hG^+Ia44LrfSfqtg$4{%qBDM z_9-O#3V+2~W$dW0G)R7l_R_vw(KSkC--u&%Rs^Io&*?R=`)6BN64>6>)`TxyT_(Rd zUn+aIl1mPa#Jse9B3`!T=|e!pIp$(8ZOe0ao?nS7o?oKlj zypC-fMj1DHIDrh1unUI1vp=-Fln;I9e7Jvs3wj*^_1&W|X} zZSL|S|Bb@CV*YC_-T&2!Ht3b6?)d`tHOP?rA;;t#zaXa0Sc;vGnV0BLIf8f-r{QHh z*Zp`4_ItlOR7{u(K+!p_oLDmaAkNag*l4#29F2b_A*0oz0T|#-&f*;c#<`^)(W@gm z#k9k=t%u8<+C1fNUA{Fh7~wgPrEZZ#(6aBI%6bR4RO(e1(ZocjoDek4#MTgZD>1NG zy9~yoZfWYfwe&S-(zk4o6q6o?2*~DOrJ(%5wSnEJMVOKCzHd z=Yhm+HLzoDl{P*Ybro7@sk1!Ez3`hE+&qr7Rw^2glw^M(b(NS2!F|Q!mi|l~lF94o z!QiV)Q{Z>GO5;l1y!$O)=)got;^)%@v#B!ZEVQy1(BJApHr5%Zh&W|gweD+%Ky%CO ztr45vR*y(@*Dg_Qw5v~PJtm^@Lyh*zRuT6~(K+^HWEF{;R#L$vL2!_ndBxCtUvZ(_ zauI7Qq}ERUWjr&XW9SwMbU>*@p)(cuWXCxRK&?ZoOy>2VESII53iPDP64S1pl{NsC zD;@EGPxs&}$W1;P6BB9THF%xfoLX|4?S;cu@$)9OdFst-!A7T{(LXtdNQSx!*GUSIS_lyI`da8>!y_tpJb3Zuf0O*;2y?HCfH z5QT6@nL|%l3&u4;F!~XG9E%1YwF*Fgs5V&uFsx52*iag(?6O|gYCBY3R{qhxT-Etb zq(E%V=MgQnuDGEKOGsmBj9T0-nmI%zys8NSO>gfJT4bP>tI>|ol@ zDt(&SUKrg%cz>AmqtJKEMUM;f47FEOFc%Bbmh~|*#E zDd!Tl(wa)ZZIFwe^*)4>{T+zuRykc3^-=P1aI%0Mh}*x7%SP6wD{_? zisraq`Las#y-6{`y@CU3Ta$tOl|@>4qXcB;1bb)oH9kD6 zKym@d$ zv&PZSSAV1Gwwzqrc?^_1+-ZGY+3_7~a(L+`-WdcJMo>EWZN3%z4y6JyF4NR^urk`c z?osO|J#V}k_6*9*n2?j+`F{B<%?9cdTQyVNm8D}H~T}?HOCXt%r7#2hz97Gx#X%62hyaLbU z_ZepP0<`<;eABrHrJAc!_m?kmu#7j}{empH@iUIEk^jk}^EFwO)vd7NZB=&uk6JG^ zC>xad8X$h|eCAOX&MaX<$tA1~r|hW?-0{t4PkVygTc`yh39c;&efwY(-#;$W)+4Xb z$XFsdG&;@^X`aynAMxsq)J#KZXX!sI@g~YiJdHI~r z$4mj_?S29sIa4c$z)19JmJ;Uj?>Kq=0XuH#k#};I&-6zZ_&>)j>UR0XetRO!-sjF< zd_6b1A2vfi++?>cf}s{@#BvTD|a%{9si7G}T+8ZnwuA z1k8c%lgE<-7f~H`cqgF;qZ|$>R-xNPA$25N1WI3#n%gj}4Ix}vj|e=x)B^roGQpB) zO+^#nO2 zjzJ9kHI6nI5ni&V_#5> z!?<7Qd9{|xwIf4b0bRc;zb}V4>snRg6*wl$Xz`hRDN8laL5tg&+@Dv>U^IjGQ}*=XBnXWrwTy;2nX?<1rkvOs#u(#qJ=A zBy>W`N!?%@Ay=upXFI}%LS9bjw?$h)7Dry0%d}=v0YcCSXf9nnp0tBKT1eqZ-4LU` zyiXglKRX)gtT0VbX1}w0f2ce8{$WH?BQm@$`ua%YP8G@<$n13D#*(Yd5-bHfI8!on zf5q4CPdgJLl;BqIo#>CIkX)G;rh|bzGuz1N%rr+5seP${mEg$;uQ3jC$;TsR&{IX< z;}7j3LnV+xNn^$F1;QarDf6rNYj7He+VsjJk6R@0MAkcwrsq4?(~`GKy|mgkfkd1msc2>%B!HpZ~HOzj}kl|ZF(IqB=D6ZTVcKe=I7)LlAI=!XU?J*i#9VXeKeaG zwx_l@Z(w`)5Cclw`6kQKlS<;_Knj)^Dh2pL`hQo!=GPOMR0iqEtx12ORLpN(KBOm5 zontAH5X5!9WHS_=tJfbACz@Dnkuw|^7t=l&x8yb2a~q|aqE_W&0M|tI7@ilGXqE)MONI8p67OiQGqKEQWw;LGga=ZM1;{pSw1jJK_y$vhY6 ztFrV7-xf>lbeKH1U)j3R=?w*>(Yh~NNEPVmeQ8n}0x01$-o z2Jyjn+sXhgOz>AzcZ zAbJZ@f}MBS0lLKR=IE{z;Fav%tcb+`Yi*!`HTDPqSCsFr>;yt^^&SI2mhKJ8f*%ji zz%JkZGvOn{JFn;)5jf^21AvO-9nRzsg0&CPz;OEn07`CfT@gK4abFBT$Mu?8fCcscmRkK+ zbAVJZ~#_a z{|(FFX}~8d3;DW8zuY9?r#Dt>!aD>} zlYw>D7y#eDy+PLZ&XKIY&Df0hsLDDi(Yrq8O==d30RchrUw8a=Eex>Dd?)3+k=}Q> z-b85lun-V$I}86Vg#l1S@1%=$2BQD5_waAZKQfJ${3{b2SZ#w1u+jMr{dJMvI|Og= zpQ9D={XK|ggbe04z { + + private val server: Server by inject(pluginId) + + override operator fun getValue(thisRef: Any?, property: KProperty<*>): Player? { + return server.getPlayer(uuid) + } +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/delegate/DelegateWorld.kt b/src/main/kotlin/fr/distractic/bukkit/api/delegate/DelegateWorld.kt new file mode 100644 index 00000000..b787a348 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/delegate/DelegateWorld.kt @@ -0,0 +1,22 @@ +package fr.distractic.bukkit.api.delegate + +import fr.distractic.bukkit.api.koin.inject +import org.bukkit.Server +import org.bukkit.World +import java.util.* +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +/** + * Class to delegate the retrieve of a world through Bukkit. + * @property uuid World's UUID. + * @property server Server to find the world. + */ +public class DelegateWorld(pluginId: String, public val uuid: UUID) : ReadOnlyProperty { + + public val server: Server by inject(pluginId) + + override operator fun getValue(thisRef: Any?, property: KProperty<*>): World? { + return server.getWorld(uuid) + } +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_CommandSender.kt b/src/main/kotlin/fr/distractic/bukkit/api/extension/_CommandSender.kt new file mode 100644 index 00000000..1a7a7f45 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/extension/_CommandSender.kt @@ -0,0 +1,34 @@ +package fr.distractic.bukkit.api.extension + +import net.kyori.adventure.text.format.NamedTextColor +import org.bukkit.command.CommandSender + +/** + * Send a error message to a sender. + * @receiver Sender that will receive the message. + * @param message Message. + */ +public fun CommandSender.sendMessageError(message: String): Unit = sendMessage(text { + content(message) + color(NamedTextColor.RED) +}) + +/** + * Verify if a sender has several permissions. + * Iterate on all permissions and check the presence with the function [CommandSender.hasPermission]. + * @receiver Sender with permissions. + * @param permissions Bukkit Permissions. + * @return `true` if the sender has all permission, `false` otherwise. + */ +@Suppress("NOTHING_TO_INLINE") +public inline fun CommandSender.hasPermissions(vararg permissions: String): Boolean = permissions.all(this::hasPermission) + +/** + * Verify if a sender has several permissions. + * Iterate on all permissions and check the presence with the function [CommandSender.hasPermission]. + * @receiver Sender with permissions. + * @param permissions Bukkit Permissions. + * @return `true` if the sender has all permission, `false` otherwise. + */ +@Suppress("NOTHING_TO_INLINE") +public inline fun CommandSender.hasPermissions(permissions: Iterable): Boolean = permissions.all(this::hasPermission) \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Comparable.kt b/src/main/kotlin/fr/distractic/bukkit/api/extension/_Comparable.kt new file mode 100644 index 00000000..11d5282b --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/extension/_Comparable.kt @@ -0,0 +1,15 @@ +package fr.distractic.bukkit.api.extension + +/** + * Get the lowest and highest value in a specific index. + * The lowest value is placed at the index 0 and highest at the index 1. + * + * @param a First value. + * @param b Second value. + * @return Both values with a defined order. + */ +public fun minMax(a: T, b: T): Pair where T : Comparable = if (a <= b) { + a to b +} else { + b to a +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Component.kt b/src/main/kotlin/fr/distractic/bukkit/api/extension/_Component.kt new file mode 100644 index 00000000..c635be25 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/extension/_Component.kt @@ -0,0 +1,82 @@ +package fr.distractic.bukkit.api.extension + +import net.kyori.adventure.text.* +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +@Suppress("NOTHING_TO_INLINE") +public inline fun Component.toText(): String { + return LegacyComponentSerializer.legacySection().serialize(this) +} + +/** + * Creates a text component using a builder. + * @param builder Function to build the component with the component builder. + * @return The component built. + */ +public inline fun text(builder: TextComponent.Builder.() -> Unit): TextComponent { + contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + return Component.text().apply(builder).build() +} + +/** + * Append a component build into the current component builder. + * @receiver Component builder. + * @param builder Function to build the child component with a component builder. + * @return The current component builder. + */ +public inline fun TextComponent.Builder.appendText(builder: TextComponent.Builder.() -> Unit): TextComponent.Builder { + contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + return append(Component.text().apply(builder).build()) +} + +/** + * Creates a keybind component using a builder. + * @param builder Function to build the component with the component builder. + * @return The component built. + */ +public inline fun keybind(builder: KeybindComponent.Builder.() -> Unit): KeybindComponent { + contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + return Component.keybind().apply(builder).build() +} + +/** + * Creates a score component using a builder. + * @param builder Function to build the component with the component builder. + * @return The component built. + */ +public inline fun score(builder: ScoreComponent.Builder.() -> Unit): ScoreComponent { + contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + return Component.score().apply(builder).build() +} + +/** + * Creates a block NBT component using builder. + * @param builder Function to build the component with the component builder. + * @return The component built. + */ +public inline fun blockNBT(builder: BlockNBTComponent.Builder.() -> Unit): BlockNBTComponent { + contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + return Component.blockNBT().apply(builder).build() +} + +/** + * Creates an entity NBT component using builder. + * @param builder Function to build the component with the component builder. + * @return The component built. + */ +public inline fun entityNBT(builder: EntityNBTComponent.Builder.() -> Unit): EntityNBTComponent { + contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + return Component.entityNBT().apply(builder).build() +} + +/** + * Creates a translatable component using builder. + * @param builder Function to build the component with the component builder. + * @return The component built. + */ +public inline fun translatable(builder: TranslatableComponent.Builder.() -> Unit): TranslatableComponent { + contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + return Component.translatable().apply(builder).build() +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_CoroutineScope.kt b/src/main/kotlin/fr/distractic/bukkit/api/extension/_CoroutineScope.kt new file mode 100644 index 00000000..a2095ce1 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/extension/_CoroutineScope.kt @@ -0,0 +1,58 @@ +package fr.distractic.bukkit.api.extension + +import fr.distractic.bukkit.api.schedule.SchedulerTask +import kotlinx.coroutines.* +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.coroutineContext +import kotlin.time.Duration + +/** + * Calls the specified suspending block with a given coroutine context into the [CoroutineScope], suspends until it completes, and returns + * the result. + * + * The resulting context for the [block] is derived by merging the current [coroutineContext] with the + * specified context using `coroutineContext + context` (see [CoroutineContext.plus]). + * This suspending function is cancellable. It immediately checks for cancellation of + * the resulting context and throws [CancellationException] if it is not [active][CoroutineContext.isActive]. + * + * This function uses dispatcher from the new context, shifting execution of the [block] into the + * different thread if a new dispatcher is specified, and back to the original dispatcher + * when it completes. Note that the result of `withContext` invocation is + * dispatched into the original context in a cancellable way with a **prompt cancellation guarantee**, + * which means that if the original [coroutineContext], in which `withContext` was invoked, + * is cancelled by the time its dispatcher starts to execute the code, + * it discards the result of `withContext` and throws [CancellationException]. + * + * The cancellation behaviour described above is enabled if and only if the dispatcher is being changed. + * For example, when using `withContext(NonCancellable) { ... }` there is no change in dispatcher and + * this call will not be cancelled neither on entry to the block inside `withContext` nor on exit from it. + */ +public suspend inline fun withScopeContext(scope: CoroutineScope, noinline block: suspend CoroutineScope.() -> T): T { + return withContext(scope.coroutineContext, block) +} + +/** + * Create a new scheduler with a task and run it. + * The [CoroutineScope] for the scheduler is a children of the [CoroutineScope] receiver. + * @receiver CoroutineScope to launch the task. + * @param delay Time to wait between each execution. + * @param body Task executed in the coroutine context each time. + * @return The instance of the scheduler where the task is run. + */ +public fun CoroutineScope.scheduledTask( + delay: Duration, + body: suspend SchedulerTask.Task.() -> Unit +): SchedulerTask = scheduler(delay).apply { + addUnsafe(body = body) + start() +} + +/** + * Create a new scheduler without task. + * The [CoroutineScope] for the scheduler is a children of the [CoroutineScope] receiver. + * @receiver CoroutineScope to launch the task. + * @param delay Time to wait between each execution. + * @return The instance of the scheduler. + */ +public fun CoroutineScope.scheduler(delay: Duration): SchedulerTask = + SchedulerTask(this, delay) \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Duration.kt b/src/main/kotlin/fr/distractic/bukkit/api/extension/_Duration.kt new file mode 100644 index 00000000..b8d80c05 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/extension/_Duration.kt @@ -0,0 +1,45 @@ +package fr.distractic.bukkit.api.extension + +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds + +/** + * Number of milliseconds corresponding to one tick. + */ +public const val MILLISECOND_PER_TICK: Int = 50 + +/** + * Get an instance of [Duration] corresponding to the time of ticks. + * 1 tick corresponding to 50 milliseconds, 20 ticks to 1 second. + */ +public val UInt.ticks: Duration get() = toInt().ticks + +/** + * Get an instance of [Duration] corresponding to the time of ticks. + * 1 tick corresponding to 50 milliseconds, 20 ticks to 1 second. + */ +public val Int.ticks: Duration get() = (this * MILLISECOND_PER_TICK).milliseconds + +/** + * Get an instance of [Duration] corresponding to the time of ticks. + * 1 tick corresponding to 50 milliseconds, 20 ticks to 1 second. + */ +public val ULong.ticks: Duration get() = toLong().ticks + +/** + * Get an instance of [Duration] corresponding to the time of ticks. + * 1 tick corresponding to 50 milliseconds, 20 ticks to 1 second. + */ +public val Long.ticks: Duration get() = (this * MILLISECOND_PER_TICK).milliseconds + +/** + * Get an instance of [Duration] corresponding to the time of ticks. + * 1 tick corresponding to 50 milliseconds, 20 ticks to 1 second. + */ +public val UShort.ticks: Duration get() = toShort().ticks + +/** + * Get an instance of [Duration] corresponding to the time of ticks. + * 1 tick corresponding to 50 milliseconds, 20 ticks to 1 second. + */ +public val Short.ticks: Duration get() = (this * MILLISECOND_PER_TICK).milliseconds \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Event.kt b/src/main/kotlin/fr/distractic/bukkit/api/extension/_Event.kt new file mode 100644 index 00000000..886858c1 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/extension/_Event.kt @@ -0,0 +1,17 @@ +package fr.distractic.bukkit.api.extension + +import org.bukkit.entity.Damageable +import org.bukkit.event.entity.EntityDamageEvent + +/** + * Future life of the damaged entity. + * @return If the entity is not damageable returns null, otherwise return the future health. + */ +public fun EntityDamageEvent.finalDamagedHealth(): Double? { + val damaged = entity + return if (damaged is Damageable) { + damaged.health - finalDamage + } else { + null + } +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_ItemStack.kt b/src/main/kotlin/fr/distractic/bukkit/api/extension/_ItemStack.kt new file mode 100644 index 00000000..7e605ece --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/extension/_ItemStack.kt @@ -0,0 +1,269 @@ +package fr.distractic.bukkit.api.extension + +import org.bukkit.Material +import org.bukkit.inventory.ItemStack +import java.io.ByteArrayOutputStream +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Type of this item. + */ +public var ItemStack.material: Material + get() = type + set(value) { + type = value + } + +/** + * Utility method to build [ItemStack]. + * @param builder Builder function. + * @return Instance of the item who is created. + */ +public inline fun item(builder: ItemStack.() -> Unit): ItemStack { + contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + return ItemStack(builder) +} + +/** + * Utility method to build [ItemStack]. + * @param builder Builder function. + * @return Instance of the item who is created. + */ +public inline fun ItemStack(builder: ItemStack.() -> Unit): ItemStack { + contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + return ItemStack(Material.AIR, builder) +} + +/** + * Utility method to build [ItemStack]. + * @param material Item material. + * @param builder Builder function. + * @return Instance of the item who is created. + */ +public inline fun ItemStack(material: Material, builder: ItemStack.() -> Unit): ItemStack { + contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + return ItemStack(material).apply(builder) +} + +/** + * Filter the array to retrieve items with only a type different of [Material.AIR]. + * @receiver Array of items. + * @return List of item without type [Material.AIR]. + */ +public fun Array.filterNotAir(): List = filter { it.type != Material.AIR } + +/** + * Filter the iterable to retrieve items with only a type different of [Material.AIR]. + * @receiver Iterable of items. + * @return List of item without type [Material.AIR]. + */ +public fun Iterable.filterNotAir(): List = filter { it.type != Material.AIR } + +/** + * Filter the sequence to retrieve items with only a type different of [Material.AIR]. + * @receiver Sequence of items. + * @return Sequence of item without type [Material.AIR]. + */ +public fun Sequence.filterNotAir(): Sequence = filter { it.type != Material.AIR } + +/** + * Get the content of the array to transform it into a map with the index and the item. + * Only the item not null and with a type different of [Material.AIR] is insert into the result. + * @receiver Array of item. + * @return Map of item with index. + */ +public fun Array.itemsIndexed(): Map { + return asIterable().itemsIndexed() +} + +/** + * Get the content of the sequence to transform it into a map with the index and the item. + * Only the item not null and with a type different of [Material.AIR] is insert into the result. + * @receiver Sequence of item. + * @return Map of item with index. + */ +public fun Sequence.itemsIndexed(): Map { + return asIterable().itemsIndexed() +} + +/** + * Get the content of the iterable instance to transform it into a map with the index and the item. + * Only the item not null and with a type different of [Material.AIR] is insert into the result. + * @receiver Iterable of item. + * @return Map of item with index. + */ +public fun Iterable.itemsIndexed(): Map { + val result = mutableMapOf() + forEachIndexed { index, item -> + if (item != null && item.type != Material.AIR) { + result[index] = item + } + } + return result +} + +/** + * Serialize a map of item with index to an encoded String with Base64. + * @receiver Items with indexes. + * @return The String encoded with items and indexes serialized. + */ +public fun Map.serializeToBase64(): String { + return serializeToBytes().encodeBase64ToString() +} + +/** + * Serialize a map of item with index to a byte array. + * @receiver Items with indexes. + * @return Byte array of items and indexes serialized. + */ +public fun Map.serializeToBytes(): ByteArray { + return ByteArrayOutputStream().use { os -> + os.buffered().use { + it.write(size) + + forEach { (index, item) -> + it.write(index) + val bytes = item.serializeAsBytes() + it.write(bytes.size) + it.write(bytes) + } + + it.flush() + os.toByteArray() + } + } +} + +/** + * Deserialize an encoded String with Base64 to a map of items and indexes. + * @receiver Items with indexes serialized. + * @return The map built from the String deserialized. + */ +public fun String.deserializeBase64ItemsIndexed(): Map { + return decodeBase64Bytes().deserializeItemsIndexed() +} + +/** + * Deserialize a byte array to a map of items and indexes. + * @receiver Items with indexes serialized. + * @return The map built from the byte array. + */ +public fun ByteArray.deserializeItemsIndexed(): Map { + return inputStream().buffered().use { + val size = it.read() + val itemsIndexed = HashMap(size) + + for (index in 0 until size) { + itemsIndexed[it.read()] = ItemStack.deserializeBytes(it.readNBytes(it.read())) + } + + itemsIndexed + } +} + +/** + * Serialize an array of item to an encoded String with Base64. + * + * @receiver Items. + * @return The String encoded with items serialized. + */ +public fun Array.serializeToBase64(): String { + return serializeToBytes().encodeBase64ToString() +} + +/** + * Serialize an array of item to a byte array. + * @receiver Items. + * @return Byte array of items serialized. + */ +public fun Array.serializeToBytes(): ByteArray { + return serializeItemsToBytes(iterator()) { size } +} + +/** + * Serialize a collection of item to an encoded String with Base64. + * @receiver Items. + * @return The String encoded with items serialized. + */ +public fun Collection.serializeToBase64(): String { + return serializeToBytes().encodeBase64ToString() +} + +/** + * Serialize a collection of item to a byte array. + * @receiver Items. + * @return Byte array of items serialized. + */ +public fun Collection.serializeToBytes(): ByteArray { + return serializeItemsToBytes(iterator()) { size } +} + +/** + * Serialize a set of item to a byte array. + * @param items Iterator of items. + * @param size Get the size of the iterator. + * @return Byte array of items serialized. + */ +public inline fun serializeItemsToBytes(items: Iterator, size: () -> Int): ByteArray { + return ByteArrayOutputStream().use { os -> + os.buffered().use { + val itSize = size() + it.write(itSize) + + var counter = 0 + while (items.hasNext() && counter < itSize) { + // With some part of the bukkit api, the item array + // is noted as @NotNull, but the content can be null. + val item = items.next() + val bytes = item.serializeAsBytes() + it.write(bytes.size) + it.write(bytes) + counter++ + } + + it.flush() + os.toByteArray() + } + } +} + +/** + * Deserialize an encoded String with Base64 to a list of items. + * @receiver Items serialized. + * @return The list built from the String deserialized. + */ +public fun String.deserializeBase64ItemsToList(): List { + return decodeBase64Bytes().deserializeItemsToList() +} + +/** + * Deserialize a byte array to a list of items. + * @receiver Items serialized. + * @return The list built from the byte array. + */ +public fun ByteArray.deserializeItemsToList(): List { + return inputStream().buffered().use { + List(it.read()) { _ -> ItemStack.deserializeBytes(it.readNBytes(it.read())) } + } +} + +/** + * Deserialize an encoded String with Base64 to an array of items. + * @receiver Items serialized. + * @return The array built from the String deserialized. + */ +public fun String.deserializeBase64ItemsToArray(): Array { + return decodeBase64Bytes().deserializeItemsToArray() +} + +/** + * Deserialize a byte array to an array of items. + * @receiver Items serialized. + * @return The array built from the byte array. + */ +public fun ByteArray.deserializeItemsToArray(): Array { + return inputStream().buffered().use { + Array(it.read()) { _ -> ItemStack.deserializeBytes(it.readNBytes(it.read())) } + } +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_JavaPlugin.kt b/src/main/kotlin/fr/distractic/bukkit/api/extension/_JavaPlugin.kt new file mode 100644 index 00000000..c0440036 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/extension/_JavaPlugin.kt @@ -0,0 +1,38 @@ +package fr.distractic.bukkit.api.extension + +import com.github.shynixn.mccoroutine.bukkit.registerSuspendingEvents +import fr.distractic.bukkit.api.item.CraftBuilder +import org.bukkit.NamespacedKey +import org.bukkit.event.Listener +import org.bukkit.plugin.java.JavaPlugin +import java.util.* +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + + +/** + * Register a new recipe craft in the server. + * @receiver Java plugin associated to the recipe. + * @param builder Builder to create recipe. + */ +public inline fun JavaPlugin.registerCraft( + key: String = UUID.randomUUID().toString().lowercase(), + builder: CraftBuilder.() -> Unit +): Boolean { + contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + val craftBuilder = CraftBuilder().apply(builder) + val namespace = NamespacedKey(this, key) + val recipe = craftBuilder.build(namespace) + return server.addRecipe(recipe) +} + +/** + * Register a new suspendable listener for the plugin. + * Support listener with non-suspend and suspend methods. + * @receiver Instance of the plugin that will receive the listener. + * @param listenerBuilder Builder to create a listener. + */ +public inline fun JavaPlugin.registerListener(listenerBuilder: () -> Listener) { + contract { callsInPlace(listenerBuilder, InvocationKind.EXACTLY_ONCE) } + server.pluginManager.registerSuspendingEvents(listenerBuilder(), this) +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Listener.kt b/src/main/kotlin/fr/distractic/bukkit/api/extension/_Listener.kt new file mode 100644 index 00000000..6d4f36b8 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/extension/_Listener.kt @@ -0,0 +1,90 @@ +package fr.distractic.bukkit.api.extension + +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withTimeoutOrNull +import org.bukkit.event.Event +import org.bukkit.event.EventPriority +import org.bukkit.event.HandlerList +import org.bukkit.event.Listener +import org.bukkit.plugin.java.JavaPlugin +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.time.Duration + +/** + * Unregister the listener. + */ +public fun Listener.unregister(): Unit = HandlerList.unregisterAll(this) + +/** + * Wait events corresponding to an event type. + * If the function [block] doesn't valid an event, stop the listening after the duration of [timeout]. + * Register a new listener to retrieve event. + * + * The current coroutine is suspended. When the coroutine is cancellable or resume, unregister the listener. + * @param plugin Java plugin to register the listener. + * @param priority Priority to register this event at. + * @param ignoreCancelled Whether to pass cancelled events or not. + * @param block Function to treat the received event, returns `true` to valid the event and stop the listening, `false` otherwise. + */ +public suspend inline fun waitEvent( + plugin: JavaPlugin, + timeout: Duration, + priority: EventPriority = EventPriority.NORMAL, + ignoreCancelled: Boolean = false, + crossinline block: T.() -> Boolean +) { + withTimeoutOrNull(timeout) { + waitEvent( + plugin = plugin, + priority = priority, + ignoreCancelled = ignoreCancelled, + block = block + ) + } +} + +/** + * Wait events corresponding to an event type. + * Register a new listener to retrieve event. + * + * The current coroutine is suspended. When the coroutine is cancellable or resume, unregister the listener. + * @param plugin Java plugin to register the listener. + * @param priority Priority to register this event at. + * @param ignoreCancelled Whether to pass cancelled events or not. + * @param block Function to treat the received event, returns `true` to valid the event and stop the listening, `false` otherwise. + */ +public suspend inline fun waitEvent( + plugin: JavaPlugin, + priority: EventPriority = EventPriority.NORMAL, + ignoreCancelled: Boolean = false, + crossinline block: T.() -> Boolean +) { + val listener = object : Listener {} + + suspendCancellableCoroutine { cont -> + cont.invokeOnCancellation { + listener.unregister() + } + + plugin.server.pluginManager.registerEvent( + T::class.java, + listener, + priority, + { _, event -> + if (event !is T) return@registerEvent + try { + if (event.block()) { + listener.unregister() + cont.resume(Unit) + } + } catch (ex: Throwable) { + listener.unregister() + cont.resumeWithException(ex) + } + }, + plugin, + ignoreCancelled + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Location.kt b/src/main/kotlin/fr/distractic/bukkit/api/extension/_Location.kt new file mode 100644 index 00000000..d31e1fb6 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/extension/_Location.kt @@ -0,0 +1,88 @@ +package fr.distractic.bukkit.api.extension + +import org.bukkit.Location +import org.bukkit.World + +/** + * Get the [Location.world] value from the location. + * @receiver Location. + * @return The world where is the location. + */ +public operator fun Location.component1(): World = world + +/** + * Get the [Location.x] value from the location. + * @receiver Location. + * @return Value of the coordinate x. + */ +public operator fun Location.component2(): Double = x + +/** + * Get the [Location.y] value from the location. + * @receiver Location. + * @return Value of the coordinate y. + */ +public operator fun Location.component3(): Double = y + +/** + * Get the [Location.z] value from the location. + * @receiver Location. + * @return Value of the coordinate z. + */ +public operator fun Location.component4(): Double = z + +/** + * Get the [Location.yaw] value from the location. + * @receiver Location. + * @return Value of the coordinate yaw. + */ +public operator fun Location.component5(): Float = yaw + +/** + * Get the [Location.pitch] value from the location. + * @receiver Location. + * @return Value of the coordinate pitch. + */ +public operator fun Location.component6(): Float = pitch + +/** + * Create a new location with the coordinate center to the current block. + * Define the x and z properties to the block location + 0.5. + * @receiver Location. + * @return New location corresponding to the current location center on the block. + */ +public fun Location.center(): Location = copy(x = blockX + 0.5, z = blockZ + 0.5) + +/** + * Define the position (x, y, z, pitch, yaw) and the world with the same value as the location in parameter. + * @receiver Location that will have its values modified + * @param location Location where values will be retrieved. + */ +public fun Location.copyFrom(location: Location) { + world = location.world + x = location.x + y = location.y + z = location.z + pitch = location.pitch + yaw = location.yaw +} + +/** + * Create a copy of the location. + * @receiver Location using to create a new location with the same properties. + * @param world The world in which this location resides + * @param x The x-coordinate of this new location + * @param y The y-coordinate of this new location + * @param z The z-coordinate of this new location + * @param yaw The absolute rotation on the x-plane, in degrees + * @param pitch The absolute rotation on the y-plane, in degrees + * @return Location + */ +public fun Location.copy( + world: World = this.world, + x: Double = this.x, + y: Double = this.y, + z: Double = this.z, + yaw: Float = this.yaw, + pitch: Float = this.pitch, +): Location = Location(world, x, y, z, yaw, pitch) \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_MerchantRecipe.kt b/src/main/kotlin/fr/distractic/bukkit/api/extension/_MerchantRecipe.kt new file mode 100644 index 00000000..24934196 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/extension/_MerchantRecipe.kt @@ -0,0 +1,51 @@ +package fr.distractic.bukkit.api.extension + +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.MerchantRecipe + +/** + * Constructor helper to create an instance of [MerchantRecipe][org.bukkit.inventory.MerchantRecipe]. + * @param result ItemStack + * @param maxUses The amount by which the demand influences + * the amount of the first ingredient is scaled by the recipe's. + * @param uses Number of times this trade has been used. + * @param experienceReward Whether to reward experience to the player for the trade. + * @param villagerExperience Amount of experience the villager earns from this trade. + * @param priceMultiplier price multiplier, can never be below zero. + * @param demand This value is periodically updated by the + * villager that owns this merchant recipe based on how often the recipe has + * been used since it has been last restocked in relation to its. + * @param specialPrice This value is dynamically + * updated whenever a player starts and stops trading with a villager that owns + * this merchant recipe. It is based on the player's individual reputation with + * the villager, and the player's currently active status effects (see + * {@link PotionEffectType#HERO_OF_THE_VILLAGE}). The influence of the player's + * reputation on the special price is scaled by the recipe's. + * @param ignoreDiscounts Whether all discounts on this trade should be ignored. + * @param ingredients List of ingredients necessary to obtain [result] item. + * @return New instance of [MerchantRecipe][org.bukkit.inventory.MerchantRecipe]. + */ +public fun MerchantRecipe( + result: ItemStack, + maxUses: Int, + uses: Int = 0, + experienceReward: Boolean = false, + villagerExperience: Int = 0, + priceMultiplier: Float = 0.0F, + demand: Int = 0, + specialPrice: Int = 0, + ignoreDiscounts: Boolean = false, + ingredients: List = emptyList() +): MerchantRecipe = MerchantRecipe( + result, + uses, + maxUses, + experienceReward, + villagerExperience, + priceMultiplier, + demand, + specialPrice, + ignoreDiscounts +).also { + it.ingredients = ingredients +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_PersistentDataHolder.kt b/src/main/kotlin/fr/distractic/bukkit/api/extension/_PersistentDataHolder.kt new file mode 100644 index 00000000..61fab58c --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/extension/_PersistentDataHolder.kt @@ -0,0 +1,17 @@ +package fr.distractic.bukkit.api.extension + +import org.bukkit.persistence.PersistentDataContainer +import org.bukkit.persistence.PersistentDataHolder +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Open the data container and manage info in it. + * @receiver PersistentDataHolder. + * @param block Function to use data container. + * @return Type of the returns type. + */ +public inline fun PersistentDataHolder.dataContainer(block: PersistentDataContainer.() -> T): T { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return persistentDataContainer.block() +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Player.kt b/src/main/kotlin/fr/distractic/bukkit/api/extension/_Player.kt new file mode 100644 index 00000000..00acd217 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/extension/_Player.kt @@ -0,0 +1,39 @@ +package fr.distractic.bukkit.api.extension + +import com.destroystokyo.paper.profile.PlayerProfile +import org.bukkit.entity.Player +import org.bukkit.inventory.ItemStack +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Allows to edit the profile of the player. + * The method must be called into the server thread. + */ +public inline fun Player.editProfile(editor: PlayerProfile.() -> Unit) { + contract { callsInPlace(editor, InvocationKind.EXACTLY_ONCE) } + playerProfile = playerProfile.apply(editor) +} + +/** + * Check if the [item] is equals to the one of the item in player's hands. + * @receiver Player. + * @param item Item to compare the item in hands. + * @return `true` if present in one of hand, `false` otherwise. + */ +@Suppress("NOTHING_TO_INLINE") +public inline fun Player.itemInHand(item: ItemStack): Boolean = itemInHand { + it == item +} + +/** + * Use the lambda [finder] to check if a specific item is present in one of both hands. + * @receiver Player. + * @param finder Lambda method to compare item. + * @return `true` if present in one of hand, `false` otherwise. + */ +public inline fun Player.itemInHand(finder: (ItemStack) -> Boolean): Boolean { + contract { callsInPlace(finder, InvocationKind.AT_LEAST_ONCE) } + val inventory = inventory + return finder(inventory.itemInMainHand) || finder(inventory.itemInOffHand) +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_PlayerProfile.kt b/src/main/kotlin/fr/distractic/bukkit/api/extension/_PlayerProfile.kt new file mode 100644 index 00000000..0bda0b70 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/extension/_PlayerProfile.kt @@ -0,0 +1,28 @@ +package fr.distractic.bukkit.api.extension + +import com.destroystokyo.paper.profile.PlayerProfile +import com.destroystokyo.paper.profile.ProfileProperty + +/** + * Name of the property to set and get textures. + */ +public const val PROPERTY_TEXTURES: String = "textures" + +/** + * Set a new texture for the profile. + * @receiver Profile of a player. + * @param skin Skin in string format. + * @param signature Signature of the skin for validation. + */ +@Suppress("NOTHING_TO_INLINE") +public inline fun PlayerProfile.setTextures(skin: String, signature: String? = null) { + setProperty(ProfileProperty(PROPERTY_TEXTURES, skin, signature)) +} + +/** + * Find the property containing the textures' data. + * @receiver Profile of a player. + * @return The property with the name, `null` otherwise. + */ +@Suppress("NOTHING_TO_INLINE") +public inline fun PlayerProfile.getTexturesProperty(): ProfileProperty? = properties.find { it.name == PROPERTY_TEXTURES } \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Runnable.kt b/src/main/kotlin/fr/distractic/bukkit/api/extension/_Runnable.kt new file mode 100644 index 00000000..ba256ead --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/extension/_Runnable.kt @@ -0,0 +1,43 @@ +package fr.distractic.bukkit.api.extension + +import com.github.shynixn.mccoroutine.bukkit.asyncDispatcher +import com.github.shynixn.mccoroutine.bukkit.minecraftDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.withContext +import org.bukkit.plugin.Plugin +import org.bukkit.scheduler.BukkitRunnable + +/** + * Create a new bukkit runnable with the body as the function sent in parameter. + * @param task Function that will be processed when the bukkit runnable will be run. + * @return New instance of bukkit runnable. + */ +public inline fun BukkitRunnable(crossinline task: BukkitRunnable.() -> Unit): BukkitRunnable { + return object : BukkitRunnable() { + override fun run() { + task() + } + } +} + +/** + * Execute the function into the primary server thread in bukkit runnable. + * The current coroutine will be suspended. + * @param block Code that will be executed in the primary thread of the server. + * @return T Instance returned after the execution of the task. + */ +public suspend inline fun onPrimaryThread( + plugin: Plugin, + noinline block: CoroutineScope.() -> T +): T = withContext(plugin.minecraftDispatcher, block) + +/** + * Execute the function asynchronously into a bukkit thread (other than primary thread). + * The current coroutine will be suspended. + * @param block Code that will be executed in a second thread of bukkit. + * @return T Instance returned after the execution of the task. + */ +public suspend inline fun onAsyncThread( + plugin: Plugin, + noinline block: CoroutineScope.() -> T +): T = withContext(plugin.asyncDispatcher, block) \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_String.kt b/src/main/kotlin/fr/distractic/bukkit/api/extension/_String.kt new file mode 100644 index 00000000..fa45a794 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/extension/_String.kt @@ -0,0 +1,139 @@ +@file:JvmName("StringUtils") +@file:JvmMultifileClass + +package fr.distractic.bukkit.api.extension + +import org.bukkit.ChatColor +import java.util.* + +/** + * Length of the value of UUID. + */ +public const val UUID_SIZE: Int = 36 + +/** + * First index of the dash. + */ +public const val UUID_INDEX_FIRST_DASH: Int = 8 + +/** + * Second index of the dash. + */ +public const val UUID_INDEX_SECOND_DASH: Int = 13 + +/** + * Third index of the dash. + */ +public const val UUID_INDEX_THIRD_DASH: Int = 18 + +/** + * Fourth index of the dash. + */ +public const val UUID_INDEX_FOURTH_DASH: Int = 23 + +/** + * Array containing all indexes of dashes. + * @return Array with all indexes. + */ +private fun uuidDashIndexes(): Array = + arrayOf(UUID_INDEX_FIRST_DASH, UUID_INDEX_SECOND_DASH, UUID_INDEX_THIRD_DASH, UUID_INDEX_FOURTH_DASH) + +/** + * Apply the coloration of Bukkit + * @see ChatColor.translateAlternateColorCodes + * @receiver String that will be analyzed to create a String colored + * @return A new String with the coloration of Bukkit + */ +public fun String.colored(): String = ChatColor.translateAlternateColorCodes('&', this) + +/*** + * Encodes the specified byte array into a String using the [Base64] encoding scheme. + * @receiver String to encode. + * @return A String containing the resulting Base64 encoded characters. + */ +public fun String.encodeBase64ToString(): String = Base64.getEncoder().encodeToString(this.toByteArray(Charsets.UTF_8)) + +/*** + * Encodes the specified byte array into a String using the [Base64] encoding scheme. + * @receiver Array of byte to encode. + * @return A byte array containing the resulting Base64 encoded characters. + */ +public fun ByteArray.encodeBase64ToString(): String = Base64.getEncoder().encodeToString(this) + +/** + * Decodes a Base64 encoded String into a newly-allocated byte array using the [Base64] encoding scheme. + * @receiver String to decode. + * @return A new String containing the decoded bytes. + */ +public fun String.decodeBase64ToString(): String = Base64.getDecoder().decode(this).decodeToString() + +/** + * Decodes a Base64 encoded String into a newly-allocated byte array using the [Base64] encoding scheme. + * @receiver Array of byte< to decode. + * @return A byte array containing the decoded bytes. + */ +public fun String.decodeBase64Bytes(): ByteArray = Base64.getDecoder().decode(this) + +/** + * Creates a [UUID] from the string standard. + * The string must have the strict format of UUID (with dashes). + * If the string cannot be converted to UUID, returns null. + * @receiver String with UUID format. + * @return The UUID instance equals to the string value or null if the format is not valid. + */ +public fun String.toUUIDStrictOrNull(): UUID? = try { + toUUIDStrict() +} catch (_: Exception) { + null +} + +/** + * Creates a [UUID] from the string standard. + * The string must have the strict format of UUID (with dashes). + * If the string cannot be converted to UUID, throws an exception. + * @receiver String with UUID format. + * @return The UUID instance equals to the string value. + * @throws IllegalArgumentException Exception if the value is not a valid uuid. + */ +@Throws(IllegalArgumentException::class) +public fun String.toUUIDStrict(): UUID = UUID.fromString(this) + +/** + * Creates a [UUID] from a string. + * The string can have dashes or not. + * If the string cannot be converted to UUID, returns null. + * @receiver String with UUID format. + * @return The UUID instance equals to the string value or null if the format is not valid. + */ +public fun String.toUUIDOrNull(): UUID? = try { + toUUID() +} catch (_: Exception) { + null +} + +/** + * Creates a [UUID] from a string. + * The string can have dashes or not. + * If the string cannot be converted to UUID, throws an exception. + * @receiver String with UUID format. + * @return The UUID instance equals to the string value. + * @throws IllegalArgumentException Exception if the value is not a valid uuid. + */ +@Throws(IllegalArgumentException::class) +public fun String.toUUID(): UUID { + if (length == UUID_SIZE) { + return toUUIDStrict() + } + + val dashIndexes = uuidDashIndexes() + // Without dash + if (length == UUID_SIZE - dashIndexes.size) { + return StringBuilder(this).apply { + for (dashIndex in dashIndexes) { + insert(dashIndex, '-') + } + }.toString().toUUIDStrict() + } + + throw IllegalArgumentException("Invalid UUID string: $this") +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Villager.kt b/src/main/kotlin/fr/distractic/bukkit/api/extension/_Villager.kt new file mode 100644 index 00000000..e351cf2e --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/extension/_Villager.kt @@ -0,0 +1,47 @@ +package fr.distractic.bukkit.api.extension + +import org.bukkit.NamespacedKey +import org.bukkit.entity.Villager +import org.bukkit.persistence.PersistentDataType +import org.bukkit.plugin.Plugin + +/** + * Key of the [NamespacedKey] to define if a villager must be keep his job or lose it. + */ +private const val KEY_KEEP_JOB = "KEEP_JOB" + +/** + * Create an instance of [NamespacedKey] corresponding to the key about the keep of profession for a villager. + * @param plugin Plugin to use for the namespace. + * @return New instance of [NamespacedKey]. + */ +public fun namespacedKeyKeepJob(plugin: Plugin): NamespacedKey = NamespacedKey(plugin, KEY_KEEP_JOB) + +/** + * Check if a villager must be keep his profession. + * @receiver Villager. + * @param plugin Plugin to find the data. + * @return `true` if the villager must be keep his job, `false` otherwise. + */ +public fun Villager.keepProfession(plugin: Plugin): Boolean { + return dataContainer { + get(namespacedKeyKeepJob(plugin), PersistentDataType.BYTE) + } != null +} + +/** + * Define if a villager must be keep his profession or not. + * @receiver Villager. + * @param plugin Plugin to define the data. + * @param keep `true` if the villager must be keep his job, `false` otherwise. + */ +public fun Villager.keepProfession(plugin: Plugin, keep: Boolean) { + dataContainer { + val key = namespacedKeyKeepJob(plugin) + if (keep) { + set(key, PersistentDataType.BYTE, 0) + } else { + remove(key) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_World.kt b/src/main/kotlin/fr/distractic/bukkit/api/extension/_World.kt new file mode 100644 index 00000000..5594ce48 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/extension/_World.kt @@ -0,0 +1,60 @@ +package fr.distractic.bukkit.api.extension + +import kotlinx.coroutines.future.await +import org.bukkit.Chunk +import org.bukkit.Location +import org.bukkit.World +import org.bukkit.block.Block + +/** + * Requests a [Chunk] to be loaded at the given coordinates; + * + * This method makes no guarantee on how fast the chunk will load and will return the chunk when finished. + * + * You should use this method if you need a chunk but do not need it + * immediately, and you wish to let the server control the speed + * of chunk loads, keeping performance in mind. + * + * @receiver World where the chunk will be retrieved. + * @param block Block into the chunk must be retrieved. + * @param gen `true` to generate the chunk if necessary, `false` otherwise. + * @return The chunk retrieved. + */ +public suspend fun World.awaitChunkAt(block: Block, gen: Boolean = true): Chunk = + getChunkAtAsync(block, gen).await() + +/** + * Requests a [Chunk] to be loaded at the given coordinates; + * + * This method makes no guarantee on how fast the chunk will load and will return the chunk when finished. + * + * You should use this method if you need a chunk but do not need it + * immediately, and you wish to let the server control the speed + * of chunk loads, keeping performance in mind. + * + * @receiver World where the chunk will be retrieved. + * @param location Location to load the corresponding chunk from. + * @param gen `true` to generate the chunk if necessary, `false` otherwise. + * @return The chunk retrieved. + */ +public suspend fun World.awaitChunkAt(location: Location, gen: Boolean = true): Chunk = + getChunkAtAsync(location, gen).await() + +/** + * Requests a [Chunk] to be loaded at the given coordinates; + * + * This method makes no guarantee on how fast the chunk will load and will return the chunk when finished. + * + * You should use this method if you need a chunk but do not need it + * immediately, and you wish to let the server control the speed + * of chunk loads, keeping performance in mind. + * + * @receiver World where the chunk will be retrieved. + * @param x X coord. + * @param z Z coord. + * @param gen `true` to generate the chunk if necessary, `false` otherwise. + * @param urgent `true` if the load must have the priority, `false` otherwise. + * @return The chunk retrieved. + */ +public suspend fun World.awaitChunkAt(x: Int, z: Int, gen: Boolean = true, urgent: Boolean = false): Chunk = + getChunkAtAsync(x, z, gen, urgent).await() \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/item/CraftBuilder.kt b/src/main/kotlin/fr/distractic/bukkit/api/item/CraftBuilder.kt new file mode 100644 index 00000000..2798c3ed --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/item/CraftBuilder.kt @@ -0,0 +1,203 @@ +package fr.distractic.bukkit.api.item + +import fr.distractic.bukkit.api.extension.item +import fr.distractic.bukkit.api.item.exception.CraftResultMissingException +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.ShapedRecipe +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Slots with index corresponding to the index defined in minecraft for the craft table. + * @property index Index in the craft table. + */ +public enum class CraftSlot(public val index: UInt) { + + /** + * Top left position on the crafting table. + */ + TopLeft(0u), + + /** + * Top position on the crafting table. + */ + Top(1u), + + /** + * Top right position on the crafting table. + */ + TopRight(2u), + + /** + * Center left position on the crafting table. + */ + CenterLeft(3u), + + /** + * Center position on the crafting table. + */ + Center(4u), + + /** + * Center right position on the crafting table. + */ + CenterRight(5u), + + /** + * Bottom left position on the crafting table. + */ + BottomLeft(6u), + + /** + * Bottom position on the crafting table. + */ + Bottom(7u), + + /** + * Bottom right position on the crafting table. + */ + BottomRight(8u) +} + +/** + * Builder to create a [ShapedRecipe]. + * @property craft Array to define the position for each item. + * @property result Result item of the crafting. + */ +public class CraftBuilder { + + /** + * Storage of the item with the position assigned for the recipe's shape. + * The top left position is defined by 0 and bottom right 8 + */ + private val craft: Array = arrayOfNulls(9) + + /** + * Result item of the craft. + */ + public var result: ItemStack? = null + + /** + * Define an item stack with the material as type at a position on the craft table. + * @param positions Positions of the item. + * @param material Item type. + * @return The item created from the material. + */ + public fun set(vararg positions: CraftSlot, material: Material): ItemStack { + return ItemStack(material).also { + set(*positions, item = it) + } + } + + /** + * Define the item stack at a position on the craft table. + * @param positions Positions of the item. + * @param builder Item builder. + * @return The item built from the builder. + */ + public inline fun set(vararg positions: CraftSlot, builder: ItemStack.() -> Unit): ItemStack { + contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + return item(builder).also { + set(*positions, item = it) + } + } + + /** + * Define the item stack at a position on the craft table. + * @param positions Positions of the item. + * @param item Item. + */ + public fun set(vararg positions: CraftSlot, item: ItemStack) { + positions.forEach { + craft[it.index.toInt()] = item + } + } + + /** + * Define the value of the [result] property. + * An item is built from the builder and assign it as result of the craft. + * @param builder Item builder. + * @return The item built from the builder. + */ + public inline fun result(builder: ItemStack.() -> Unit): ItemStack { + contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + return item(builder).also(this::result) + } + + /** + * Define the value of the [result] property. + * @param item Item assign to the result of the craft. + */ + @Suppress("NOTHING_TO_INLINE") + public inline fun result(item: ItemStack) { + result = item + } + + /** + * Build the [ShapedRecipe] associated to the craft schema defined by the method [set]. + * @param key The unique recipe key. + * @return Shape of the recipe built from the data of [craft]. + */ + public fun build(key: NamespacedKey): ShapedRecipe { + val itemResult = result ?: throw CraftResultMissingException() + val shaped = ShapedRecipe(key, itemResult) + + val keyItems = associateKeyWithItem() + val lines = keysToCraftLines(keyItems) + + shaped.shape(*lines) + setNotNullIngredient(keyItems, shaped) + + return shaped + } + + /** + * Set the ingredients in the [shaped] only if it is not null. + * @param keyItems Map of association about keys and items. + * @param shaped Shape where the ingredient will be set. + */ + private fun setNotNullIngredient( + keyItems: List>, + shaped: ShapedRecipe + ) { + keyItems.asSequence() + .distinctBy { it.first } + .forEach { (key, item) -> + if (item != null) { + shaped.setIngredient(key, item) + } + } + } + + /** + * Transform the set of keys to a list of lines for the craft format. + * The keys are chunked by size of 3 (3 items by line). + * @param keys Keys associated to items. + * @return Array of 3 [String] containing each 3 [Char]. + */ + private fun keysToCraftLines(keys: List>): Array = + keys + .asSequence() + // [a,b,c,d,e,f,g,h,i] + .map { it.first } + // ["abcdefghi"] + .joinToString(separator = "") + // ["abc", "def", "ghi"] + .chunked(3) + .toTypedArray() + + /** + * Associated a key for each item in [craft]. + * If two items are similar, they will have the same key. + * @return Map with the key associate to an item. + */ + private fun associateKeyWithItem(): List> { + val itemKeys = LinkedHashMap(craft.size) + var keyIncrement = 'A' + return craft.map { + (if (it == null) ' ' else itemKeys.getOrPut(it) { keyIncrement++ }) to it + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/item/exception/CraftResultMissingException.kt b/src/main/kotlin/fr/distractic/bukkit/api/item/exception/CraftResultMissingException.kt new file mode 100644 index 00000000..55e81207 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/item/exception/CraftResultMissingException.kt @@ -0,0 +1,6 @@ +package fr.distractic.bukkit.api.item.exception + +/** + * Exception when the result item is not defined. + */ +public class CraftResultMissingException : IllegalStateException("The result item for the craft must be defined") \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/koin/CraftContext.kt b/src/main/kotlin/fr/distractic/bukkit/api/koin/CraftContext.kt new file mode 100644 index 00000000..53995e95 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/koin/CraftContext.kt @@ -0,0 +1,141 @@ +package fr.distractic.bukkit.api.koin + +import org.koin.core.Koin +import org.koin.core.KoinApplication +import org.koin.core.context.KoinContext +import org.koin.core.error.KoinAppAlreadyStartedException +import org.koin.core.module.Module +import org.koin.dsl.KoinAppDeclaration +import org.koin.dsl.ModuleDeclaration +import org.koin.dsl.module + +/** + * Wrapper for [org.koin.dsl.module] that immediately loads the module for the current [Koin] instance. + * @param id App id to find the dedicated koin instance. + * @param createdAtStart `true` to execute declaration directly, or `false` to load with lazy way. + * @param moduleDeclaration Declaration of the module + * @return The new module created. + */ +public fun loadModule( + id: String, + createdAtStart: Boolean = false, + moduleDeclaration: ModuleDeclaration +): Module { + return module(createdAtStart, moduleDeclaration).also { CraftContext.loadKoinModules(id, it) } +} + +public inline fun inject(id: String): Lazy = CraftContext.get(id).inject() + +/** + * A copy of [KoinContext] to retrieve koin instance for each application. + * This contains the [KoinApplication] and its [Koin] instance for dependency injection. + * + * @see org.koin.core.context.GlobalContext + */ +public object CraftContext { + + /** + * [Koin] instanced linked to an app id. + */ + private val koins: MutableMap> = mutableMapOf() + + /** + * Gets the [Koin] instance for an app. + * @param id App id to find the dedicated koin instance. + * @return The koin instance. + * @throws IllegalStateException [KoinApplication] has not yet been started. + */ + public fun get(id: String): Koin = getOrNull(id) ?: error("KoinApplication has not been started for the id [$id]") + + /** + * Gets the [Koin] instance or null if the [KoinApplication] has not yet been started. + * @param id App id to find the dedicated koin instance. + * @return Koin? + */ + public fun getOrNull(id: String): Koin? = koins[id]?.second + + /** Closes and removes the current [Koin] instance. */ + public fun stopKoin(id: String): Unit = synchronized(this) { + val koinInstance = koins[id] ?: return@synchronized + koinInstance.second?.close() + koins -= id + } + + /** + * Starts using the provided [KoinAppDeclaration] to create the [KoinApplication] for this context. + * + * @param appDeclaration The application declaration to start with. + * + * @throws KoinAppAlreadyStartedException The [KoinApplication] has already been instantiated. + */ + public fun startKoin(id: String, appDeclaration: KoinAppDeclaration): KoinApplication = synchronized(this) { + val koinApplication = KoinApplication.init() + appDeclaration(koinApplication) + return@synchronized startKoin(id, koinApplication) + } + + /** + * Starts using the provided [KoinApplication] as the current one for this context. + * + * @param koinApplication The application to start with. + * + * @throws KoinAppAlreadyStartedException The [KoinApplication] has already been instantiated. + */ + public fun startKoin(id: String, koinApplication: KoinApplication): KoinApplication = synchronized(this) { + register(id, koinApplication) + koinApplication.createEagerInstances() + + return koinApplication + } + + /** + * Registers a [KoinApplication] to as the current one for this context. + * + * @param koinApplication The application to registers. + * + * @throws KoinAppAlreadyStartedException The [KoinApplication] has already been instantiated. + */ + private fun register(id: String, koinApplication: KoinApplication) { + if (getOrNull(id) != null) { + throw KoinAppAlreadyStartedException("Koin Application has already been started for id [$id]") + } + + koins[id] = koinApplication to koinApplication.koin + } + + /** + * Loads a module into the [Koin] instance. + * + * @param module The module to load. + */ + public fun loadKoinModules(id: String, module: Module): Unit = synchronized(this) { + get(id).loadModules(listOf(module)) + } + + /** + * Loads modules into the [Koin] instance. + * + * @param modules The modules to load. + */ + public fun loadKoinModules(id: String, modules: List): Unit = synchronized(this) { + get(id).loadModules(modules) + } + + /** + * Unloads a module from the [Koin] instance. + * + * @param module The module to unload. + */ + public fun unloadKoinModules(id: String, module: Module): Unit = synchronized(this) { + get(id).unloadModules(listOf(module)) + } + + /** + * Unloads modules from the [Koin] instance. + * + * @param modules The modules to unload. + */ + public fun unloadKoinModules(id: String, modules: List): Unit = synchronized(this) { + get(id).unloadModules(modules) + } +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/listener/PlayerListener.kt b/src/main/kotlin/fr/distractic/bukkit/api/listener/PlayerListener.kt new file mode 100644 index 00000000..0766dfeb --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/listener/PlayerListener.kt @@ -0,0 +1,62 @@ +package fr.distractic.bukkit.api.listener + +import fr.distractic.bukkit.api.Plugin +import fr.distractic.bukkit.api.coroutine.exception.SilentCancellationException +import fr.distractic.bukkit.api.koin.inject +import fr.distractic.bukkit.api.player.Client +import fr.distractic.bukkit.api.player.ClientManager +import fr.distractic.bukkit.api.player.exception.ClientAlreadyExistsException +import kotlinx.coroutines.cancel +import org.bukkit.entity.Player +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.player.PlayerJoinEvent +import org.bukkit.event.player.PlayerQuitEvent + +/** + * Main listener to manager instance of clients for the player entering and exiting the server. + * @property plugin Java plugin. + * @property clients Client manager to store and remove client instances. + */ +public class PlayerListener(private val plugin: Plugin) : Listener { + + private val clients: ClientManager by inject(plugin.id) + + /** + * Handle the join event to create and store a new client. + * The client will be linked to the player. + * @param event Event. + */ + @EventHandler(priority = EventPriority.LOWEST) + public suspend fun onJoin(event: PlayerJoinEvent) { + val player = event.player + createAndSaveClient(player) + } + + /** + * Create a new instance of client and store it into [clients]. + * If a client is already found for the player, throw an exception. + * @param player Player linked to the client. + * @return The instance of the client. + */ + private suspend fun createAndSaveClient(player: Player): Client { + val client = plugin.createClient(player) + if (clients.putIfAbsent(player, client) != null) { + throw ClientAlreadyExistsException("A client linked to the player already exists.") + } + return client + } + + /** + * Handle the quit event to remove the client linked to the player leaving. + * The life cycle of the client will be cancelled. + * @param event Event. + */ + @EventHandler(priority = EventPriority.HIGHEST) + public suspend fun onQuit(event: PlayerQuitEvent) { + val player = event.player + val client = clients.removeClient(player) ?: return + client.cancel(SilentCancellationException("The player ${player.uniqueId} left")) + } +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/listener/VillagerListener.kt b/src/main/kotlin/fr/distractic/bukkit/api/listener/VillagerListener.kt new file mode 100644 index 00000000..36c318aa --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/listener/VillagerListener.kt @@ -0,0 +1,28 @@ +package fr.distractic.bukkit.api.listener + +import fr.distractic.bukkit.api.extension.keepProfession +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.entity.VillagerCareerChangeEvent +import org.bukkit.plugin.java.JavaPlugin +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +/** + * Listener to manage villager state. + * @property plugin Plugin. + */ +public class VillagerListener : Listener, KoinComponent { + + private val plugin: JavaPlugin by inject() + + /** + * When a villager will have his profession changed, check if a specific tag is present into the entity. + * If the tag is present, the event will be cancelled, otherwise the career will be changed. + * @param event Event when a villager will lose his job. + */ + @EventHandler + public fun onChangeCareer(event: VillagerCareerChangeEvent) { + event.isCancelled = event.entity.keepProfession(plugin) + } +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/player/Client.kt b/src/main/kotlin/fr/distractic/bukkit/api/player/Client.kt new file mode 100644 index 00000000..1b898300 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/player/Client.kt @@ -0,0 +1,24 @@ +package fr.distractic.bukkit.api.player + +import fr.distractic.bukkit.api.delegate.DelegatePlayer +import fr.distractic.bukkit.api.player.exception.PlayerNotFoundException +import kotlinx.coroutines.CoroutineScope +import org.bukkit.entity.Player +import java.util.* + +/** + * Client to store and manage data about player.* + * @property playerUUID Player's uuid. + * @property player Player linked to the client. + */ +public open class Client(pluginId: String, public val playerUUID: UUID, coroutineScope: CoroutineScope) : CoroutineScope by coroutineScope { + + public val player: Player? by DelegatePlayer(pluginId, playerUUID) + + /** + * Retrieve the instance of player. + * If the player is not found from the server, thrown an exception. + * @return The instance of player. + */ + public fun requirePlayer(): Player = player ?: throw PlayerNotFoundException("The player cannot be retrieved from the server") +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/player/ClientManager.kt b/src/main/kotlin/fr/distractic/bukkit/api/player/ClientManager.kt new file mode 100644 index 00000000..6c1318a5 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/player/ClientManager.kt @@ -0,0 +1,97 @@ +package fr.distractic.bukkit.api.player + +import org.bukkit.entity.Player + +/** + * Get a client from the player instance. + * @param player Player. + * @return The client linked to a player. + */ +public suspend inline fun ClientManager.getTypedClient(player: Player): T = getClient(player) as T + +/** + * Get a client from the key linked to a player. + * @param key Key to find a client. + * @return The client linked to a key. + */ +public suspend inline fun ClientManager.getTypedClient(key: String): T = getClient(key) as T + +/** + * Get a client from the player instance. + * @param player Player. + * @return The client linked to a player, `null` if not found. + */ +public suspend inline fun ClientManager.getTypedClientOrNull(player: Player): T? = + getClientOrNull(player) as T? + +/** + * Get a client from the key linked to a player. + * @param key Key to find a client. + * @return The client linked to a player, `null` if not found. + */ +public suspend inline fun ClientManager.getTypedClientOrNull(key: String): T? = getClientOrNull(key) as T? + +/** + * Manage the existing client present in the server. + * @property clients Synchronized mutable map of client as value and name of player as key. + */ +public interface ClientManager { + + public val clients: Map + + /** + * Put a new client in the server. + * @param client New client added. + * @return The previous value associated with key, or null there is none. + */ + public suspend fun put(player: Player, client: Client): Client? + + /** + * Put a new client in the server if no client is linked to the player. + * @param client New client added. + * @return The previous value associated with key, or null there is none. + */ + public suspend fun putIfAbsent(player: Player, client: Client): Client? + + /** + * Remove a client from the server by a Player. + * @param player Player linked to a Client. + * @return The client that was removed, null otherwise. + */ + public suspend fun removeClient(player: Player): Client? + + /** + * Get a client from the player instance. + * @param player Player. + * @return The client linked to a player. + */ + public suspend fun getClient(player: Player): Client + + /** + * Get a client from the key linked to a player. + * @param key Key to find a client. + * @return The client linked to a key. + */ + public suspend fun getClient(key: String): Client + + /** + * Get a client from the player instance. + * @param player Player. + * @return The client linked to a player, `null` if not found. + */ + public suspend fun getClientOrNull(player: Player): Client? + + /** + * Get a client from the key linked to a player. + * @param key Key to find a client. + * @return The client linked to a player, `null` if not found. + */ + public suspend fun getClientOrNull(key: String): Client? + + /** + * Check if a client is linked to a player. + * @param player Player. + * @return `true` if there is a client for the player, `false` otherwise. + */ + public suspend fun contains(player: Player): Boolean +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/player/ClientManagerImpl.kt b/src/main/kotlin/fr/distractic/bukkit/api/player/ClientManagerImpl.kt new file mode 100644 index 00000000..dabf43d5 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/player/ClientManagerImpl.kt @@ -0,0 +1,77 @@ +package fr.distractic.bukkit.api.player + +import fr.distractic.bukkit.api.player.exception.ClientNotFoundException +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import org.bukkit.entity.Player + +/** + * Manage the existing client present in the server. + * The clients are stored with the name of the player. + * @property _clients Synchronized mutable map of client as value and name of player as key. + */ +public class ClientManagerImpl : ClientManager { + + private val mutex = Mutex() + + /** + * All clients in server linked by the name of player. + */ + private val _clients = mutableMapOf() + + override val clients: Map = _clients + + override suspend fun put( + player: Player, + client: Client + ): Client? = mutex.withLock { + _clients.put(player.name, client) + } + + override suspend fun putIfAbsent( + player: Player, + client: Client + ): Client? = mutex.withLock { + _clients.putIfAbsent(getKey(player), client) + } + + override suspend fun removeClient(player: Player): Client? = mutex.withLock { + _clients.remove(getKey(player)) + } + + override suspend fun getClient(player: Player): Client = mutex.withLock { + getClient(getKey(player)) + } + + override suspend fun getClient(key: String): Client = + mutex.withLock { + getClientOrNull(key) ?: throw ClientNotFoundException("No client is linked to the name [$key]") + } + + override suspend fun getClientOrNull(player: Player): Client? = mutex.withLock { + getClientOrNull(getKey(player)) + } + + override suspend fun getClientOrNull(key: String): Client? = mutex.withLock { + _clients[key] + } + + /** + * Key use for the Map + * @param p Player that has the key + * @return The key for the Map + */ + private suspend fun getKey(p: Player): String = mutex.withLock { + p.name + } + + /** + * Check if a client is linked to a player. + * @param player Player + * @return `true` if there is a client for the player, `false` otherwise. + */ + override suspend fun contains(player: Player): Boolean = mutex.withLock { + _clients.containsKey(getKey(player)) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/player/exception/ClientAlreadyExistsException.kt b/src/main/kotlin/fr/distractic/bukkit/api/player/exception/ClientAlreadyExistsException.kt new file mode 100644 index 00000000..4c092f1e --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/player/exception/ClientAlreadyExistsException.kt @@ -0,0 +1,6 @@ +package fr.distractic.bukkit.api.player.exception + +/** + * Exception if a client is already exists for a player. + */ +public class ClientAlreadyExistsException(message: String? = null) : RuntimeException(message) \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/player/exception/ClientNotFoundException.kt b/src/main/kotlin/fr/distractic/bukkit/api/player/exception/ClientNotFoundException.kt new file mode 100644 index 00000000..62f9af61 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/player/exception/ClientNotFoundException.kt @@ -0,0 +1,6 @@ +package fr.distractic.bukkit.api.player.exception + +/** + * Exception if a client cannot be found. + */ +public class ClientNotFoundException(message: String? = null) : RuntimeException(message) \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/player/exception/PlayerNotFoundException.kt b/src/main/kotlin/fr/distractic/bukkit/api/player/exception/PlayerNotFoundException.kt new file mode 100644 index 00000000..aaa9182d --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/player/exception/PlayerNotFoundException.kt @@ -0,0 +1,6 @@ +package fr.distractic.bukkit.api.player.exception + +/** + * Exception if a player is not into the server. + */ +public class PlayerNotFoundException(message: String? = null) : RuntimeException(message) \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/player/exception/PlayerQuitException.kt b/src/main/kotlin/fr/distractic/bukkit/api/player/exception/PlayerQuitException.kt new file mode 100644 index 00000000..ac4ff006 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/player/exception/PlayerQuitException.kt @@ -0,0 +1,9 @@ +package fr.distractic.bukkit.api.player.exception + +import kotlin.coroutines.cancellation.CancellationException + +/** + * Exception throw when the player quits the server. + * Allows canceling all coroutine linked to a player. + */ +public class PlayerQuitException(message: String? = null) : CancellationException(message) \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/schedule/AbstractScheduler.kt b/src/main/kotlin/fr/distractic/bukkit/api/schedule/AbstractScheduler.kt new file mode 100644 index 00000000..43bc7356 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/schedule/AbstractScheduler.kt @@ -0,0 +1,45 @@ +package fr.distractic.bukkit.api.schedule + +import kotlinx.coroutines.* + +/** + * Allows scheduling a task into a coroutine from the [coroutineScope]. + * @property coroutineScope Scope to start the job. + * @property job Instance of the job launched. + * @property running `true` if the task is running, `false` otherwise. + */ +public abstract class AbstractScheduler( + protected val coroutineScope: CoroutineScope +) : Scheduler { + + protected var job: Job? = null + + override val running: Boolean + get() = job?.isActive == true + + override fun start() { + if (running) { + throw IllegalStateException("The scheduling is already running") + } + + job = coroutineScope.launch { + run() + } + } + + /** + * Main body of the scheduler. + * Will create the loop to execute tasks. + */ + public abstract suspend fun run() + + override fun cancel(cause: CancellationException?) { + job?.cancel(cause) + job = null + } + + override suspend fun cancelAndJoin() { + job?.cancelAndJoin() + job = null + } +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/schedule/Scheduler.kt b/src/main/kotlin/fr/distractic/bukkit/api/schedule/Scheduler.kt new file mode 100644 index 00000000..3c794faf --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/schedule/Scheduler.kt @@ -0,0 +1,29 @@ +package fr.distractic.bukkit.api.schedule + +import kotlinx.coroutines.CancellationException + +/** + * Schedule to start and stop a task using coroutine. + * @property running `true` if the scheduler is running, `false` otherwise. + */ +public interface Scheduler { + + public val running: Boolean + + /** + * Start the task. + * Throw an exception if the task is already running. + */ + public fun start() + + /** + * Stop the task. + * @param cause Reason of the cancellation. + */ + public fun cancel(cause: CancellationException? = null) + + /** + * Stop the task and wait the end of last execution. + */ + public suspend fun cancelAndJoin() +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/schedule/SchedulerTask.kt b/src/main/kotlin/fr/distractic/bukkit/api/schedule/SchedulerTask.kt new file mode 100644 index 00000000..675eca1d --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/schedule/SchedulerTask.kt @@ -0,0 +1,221 @@ +package fr.distractic.bukkit.api.schedule + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import mu.KotlinLogging +import java.util.* +import java.util.concurrent.CancellationException +import kotlin.time.Duration + +private val log = KotlinLogging.logger { } + +/** + * Allows executing code while the task is not cancelled. + * The [tasks] contains several body function that one will be executed after each [delay]. + * @property delay Time to wait between each execution. + * @property _tasks Task executed in the coroutine context each time. + * @property tasks Immutable list of task executed in the coroutine context each time. + * @property mutex Mutex to synchronized data of the scheduler. + * @property nextTaskIndex Next index of the task that will be executed. + */ +public class SchedulerTask( + coroutineScope: CoroutineScope, + private var delay: Duration, + private var delayBefore: Boolean = false, + private var stopWhenNoTask: Boolean = true +) : AbstractScheduler(coroutineScope) { + + /** + * Task of the scheduler. + * @property id Id of the task. + * @property parent Scheduler task where is registered the task. + * @property body Lambda function. + */ + public inner class Task( + public val id: String = UUID.randomUUID().toString(), + private val parent: SchedulerTask, + public val body: suspend Task.() -> Unit + ) { + + /** + * Run the [body] function. + */ + public suspend fun run() { + body() + } + + /** + * Remove the task from the parent. + */ + public suspend fun remove(): Boolean { + return parent.remove(id) + } + } + + private var nextTaskIndex = 0 + + private val _tasks = ArrayList() + private val tasks: List = _tasks + + private val mutex = Mutex() + + override suspend fun run() { + if (delayBefore) { + delay(delay) + } + + coroutineScope { + while (isActive) { + val task = getNextTaskAndShiftCursor() + if (task != null) { + runTask(task) + } + delay(delay) + } + } + } + + /** + * Lock the mutex to search the next task that will be executed. + * After have found the task, move the cursor [nextTaskIndex]. + * @return `null` if no task found into the list, the task otherwise. + */ + private suspend fun getNextTaskAndShiftCursor(): Task? = mutex.withLock { + var task = _tasks.getOrNull(nextTaskIndex) + + if (task == null) { + task = _tasks.firstOrNull() + nextTaskIndex = 1 + } else { + nextTaskIndex++ + } + task + } + + /** + * Run the task in try catch to protect the scheduler. + * @param task Task that will be run. + */ + private suspend fun runTask(task: Task) { + try { + task.run() + } catch (t: Throwable) { + log.error("Error during process of the task $task", t) + } + } + + /** + * Add a new body should be executed in the scheduler. + * @param id Id of the task created. + * @param body Lambda function. + * @return Task created. + */ + public suspend fun add(id: String = UUID.randomUUID().toString(), body: suspend Task.() -> Unit): Task = mutex.withLock { + addUnsafe(id, body) + } + + /** + * Add a new body should be executed in the scheduler. + * The operation is made with possible asynchronous effect if the scheduler is running. + * It's recommend to use this function when you are sure that the scheduler is not running. + * @param id Id of the task created. + * @param body Lambda function. + * @return Task created. + */ + public fun addUnsafe(id: String = UUID.randomUUID().toString(), body: suspend Task.() -> Unit): Task { + return createTask(id, body).also { + _tasks += it + } + } + + /** + * Add a new body should be executed in the scheduler. + * @param index Index at which the specified element is to be inserted. + * @param id Id of the task created. + * @param body Lambda function. + * @return Task created. + */ + public suspend fun addAt(index: Int, id: String = UUID.randomUUID().toString(), body: suspend Task.() -> Unit): Task = + mutex.withLock { addAtUnsafe(index, id, body) } + + /** + * Add a new body should be executed in the scheduler. + * The operation is made with possible asynchronous effect if the scheduler is running. + * It's recommend to use this function when you are sure that the scheduler is not running. + * @param index Index at which the specified element is to be inserted. + * @param id Id of the task created. + * @param body Lambda function. + * @return Task created. + */ + public fun addAtUnsafe(index: Int, id: String = UUID.randomUUID().toString(), body: suspend Task.() -> Unit): Task = + createTask(id, body).also { + _tasks.add(index, it) + } + + /** + * Create a children task. + * @param id Id of the task created. + * @param body Lambda function linked to the task. + * @return Task created. + */ + private fun createTask( + id: String, + body: suspend Task.() -> Unit + ) = Task(id, this, body) + + /** + * Remove a task from the scheduler. + * @param id Id of the task registered into the scheduler. + * @return `true` if a task has been removed, `false` otherwise. + */ + public suspend fun remove(id: String): Boolean = mutex.withLock { + removeUnsafe(id) + } + + /** + * Remove a task from the scheduler. + * The operation is made with possible asynchronous effect if the scheduler is running. + * It's recommend to use this function when you are sure that the scheduler is not running. + * @param id Id of the task registered into the scheduler. + * @return `true` if a task has been removed, `false` otherwise. + */ + public fun removeUnsafe(id: String): Boolean { + val indexOfTask = _tasks.indexOfFirst { it.id == id } + if (indexOfTask == -1) return false + removeAtUnsafe(indexOfTask) + return true + } + + /** + * Remove a task from the scheduler. + * Throws exception if no task is present at the index. + * @param index Index of the task that will be removed. + */ + public suspend fun removeAt(index: Int): Unit = mutex.withLock { + removeAtUnsafe(index) + } + + /** + * Remove the task at the index. + * Throws exception if no task is present at the index. + * The operation is made with possible asynchronous effect if the scheduler is running. + * It's recommend to use this function when you are sure that the scheduler is not running. + * If necessary, shift the next task to not skip one task. + * @param index Index of the task. + */ + public fun removeAtUnsafe(index: Int) { + _tasks.removeAt(index) + if (tasks.isEmpty()) { + if (stopWhenNoTask) { + cancel(CancellationException("No tasks left. The scheduler is disabled")) + } + nextTaskIndex = 0 + } else if (index < nextTaskIndex) { + nextTaskIndex-- + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/world/BlockPosition.kt b/src/main/kotlin/fr/distractic/bukkit/api/world/BlockPosition.kt new file mode 100644 index 00000000..8460594b --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/world/BlockPosition.kt @@ -0,0 +1,9 @@ +package fr.distractic.bukkit.api.world + +/** + * Data class representing a position of one block with x, y, z. + * @property x X value. + * @property y Y value. + * @property z Z value. + */ +public data class BlockPosition(val x: Int, val y: Int, val z: Int) \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/world/Cuboid.kt b/src/main/kotlin/fr/distractic/bukkit/api/world/Cuboid.kt new file mode 100644 index 00000000..2be930ab --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/world/Cuboid.kt @@ -0,0 +1,105 @@ +package fr.distractic.bukkit.api.world + +import fr.distractic.bukkit.api.delegate.DelegateWorld +import fr.distractic.bukkit.api.extension.minMax +import fr.distractic.bukkit.api.world.exception.WorldDifferentException +import fr.distractic.bukkit.api.world.exception.WorldNotFoundException +import org.bukkit.Location +import org.bukkit.World +import org.bukkit.block.Block +import java.util.* + +/** + * Represents an area in a minecraft world. + * @property uuidWorld World's UUID. + * @property world World retrieve from the server. + * @property minCoordinate Position with minimal coordinate's value. + * @property maxCoordinate Position with maximal coordinate's value. + */ +public class Cuboid(pluginId: String, public val uuidWorld: UUID, x1: Int, y1: Int, z1: Int, x2: Int, y2: Int, z2: Int) { + + public companion object { + + /** + * Create a cuboid using the position in location instances. + * Throws an exception if the world of both locations are different. + * @param location1 Location 1 + * @param location2 Location 2 + * @return New instance of cuboid. + */ + public fun of(pluginId: String, location1: Location, location2: Location): Cuboid { + val world1 = location1.world + val world2 = location2.world + if (world1 != world2) { + throw WorldDifferentException(world1, world2, "The locations don't have the same world") + } + return Cuboid( + pluginId, + world1.uid, + location1.blockX, + location1.blockY, + location1.blockZ, + location2.blockX, + location2.blockY, + location2.blockZ + ) + } + } + + public val world: World? by DelegateWorld(pluginId, uuidWorld) + + public val minCoordinate: BlockPosition + public val maxCoordinate: BlockPosition + + init { + val (minX, maxX) = minMax(x1, x2) + val (minY, maxY) = minMax(y1, y2) + val (minZ, maxZ) = minMax(z1, z2) + minCoordinate = BlockPosition(minX, minY, minZ) + maxCoordinate = BlockPosition(maxX, maxY, maxZ) + } + + /** + * Retrieve the instance of world. + * If the world is not found from the server, thrown an exception. + * @return The instance of world. + */ + public fun requireWorld(): World = world ?: throw WorldNotFoundException("The world cannot be retrieved from the server") + + /** + * Know if a location is in the area. + * + * @param location Location. + * @return `true` if the location is between bounds, `false` otherwise. + */ + public operator fun contains(location: Location): Boolean = location.world == world + && location.blockX in minCoordinate.x..maxCoordinate.x + && location.blockY in minCoordinate.y..maxCoordinate.y + && location.blockZ in minCoordinate.z..maxCoordinate.z + + /** + * Create an iterator to iterate on all locations present in the area. + * @return Iterator of location. + */ + public fun locationSequence(): Sequence = sequence { + val world = requireWorld() + for (x in minCoordinate.x..maxCoordinate.x) { + val xDouble = x.toDouble() + for (y in minCoordinate.y..maxCoordinate.y) { + val yDouble = y.toDouble() + for (z in minCoordinate.z..maxCoordinate.z) { + yield(Location(world, xDouble, yDouble, z.toDouble())) + } + } + } + } + + /** + * Create an iterator to iterate on all blocks present in the area. + * @return Iterator of location. + */ + public fun blockSequence(): Sequence { + return locationSequence() + .map { it.world.getBlockAt(it) } + } +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/world/exception/WorldDifferentException.kt b/src/main/kotlin/fr/distractic/bukkit/api/world/exception/WorldDifferentException.kt new file mode 100644 index 00000000..1cd57957 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/world/exception/WorldDifferentException.kt @@ -0,0 +1,9 @@ +package fr.distractic.bukkit.api.world.exception + +import org.bukkit.World + +/** + * Exception when two worlds are different. + */ +public class WorldDifferentException(public val world1: World? = null, public val world2: World? = null, message: String? = null) : + RuntimeException(message) \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/world/exception/WorldNotFoundException.kt b/src/main/kotlin/fr/distractic/bukkit/api/world/exception/WorldNotFoundException.kt new file mode 100644 index 00000000..85077da1 --- /dev/null +++ b/src/main/kotlin/fr/distractic/bukkit/api/world/exception/WorldNotFoundException.kt @@ -0,0 +1,6 @@ +package fr.distractic.bukkit.api.world.exception + +/** + * Exception if a world is not into the server. + */ +public class WorldNotFoundException(message: String? = null) : RuntimeException(message) \ No newline at end of file From bbb00d74cd5358b5e9f7a7bfc8a655ccc1c0a8af Mon Sep 17 00:00:00 2001 From: Distractic Date: Fri, 19 Aug 2022 16:26:32 +0200 Subject: [PATCH 020/143] chore: Change package and cuboid system --- .../bukkit/api/world/BlockPosition.kt | 9 -- .../fr/distractic/bukkit/api/world/Cuboid.kt | 105 ------------------ .../distractic/bukkit/api/APIPlugin.kt | 2 +- .../github}/distractic/bukkit/api/Plugin.kt | 23 ++-- .../exception/SilentCancellationException.kt | 2 +- .../bukkit/api/delegate/DelegatePlayer.kt | 4 +- .../bukkit/api/delegate/DelegateWorld.kt | 4 +- .../bukkit/api/extension/_CommandSender.kt | 2 +- .../bukkit/api/extension/_Comparable.kt | 4 +- .../bukkit/api/extension/_Component.kt | 2 +- .../bukkit/api/extension/_CoroutineScope.kt | 4 +- .../bukkit/api/extension/_Duration.kt | 2 +- .../distractic/bukkit/api/extension/_Event.kt | 2 +- .../bukkit/api/extension/_ItemStack.kt | 2 +- .../bukkit/api/extension/_JavaPlugin.kt | 4 +- .../bukkit/api/extension/_Listener.kt | 2 +- .../bukkit/api/extension/_Location.kt | 2 +- .../bukkit/api/extension/_MerchantRecipe.kt | 2 +- .../api/extension/_PersistentDataHolder.kt | 2 +- .../bukkit/api/extension/_Player.kt | 2 +- .../bukkit/api/extension/_PlayerProfile.kt | 2 +- .../bukkit/api/extension/_Runnable.kt | 2 +- .../bukkit/api/extension/_String.kt | 2 +- .../bukkit/api/extension/_Villager.kt | 2 +- .../distractic/bukkit/api/extension/_World.kt | 2 +- .../bukkit/api/item/CraftBuilder.kt | 8 +- .../exception/CraftResultMissingException.kt | 2 +- .../bukkit/api/koin/CraftContext.kt | 2 +- .../bukkit/api/listener/PlayerListener.kt | 14 +-- .../bukkit/api/listener/VillagerListener.kt | 9 +- .../distractic/bukkit/api/player/Client.kt | 6 +- .../bukkit/api/player/ClientManager.kt | 2 +- .../bukkit/api/player/ClientManagerImpl.kt | 4 +- .../exception/ClientAlreadyExistsException.kt | 2 +- .../exception/ClientNotFoundException.kt | 2 +- .../exception/PlayerNotFoundException.kt | 2 +- .../player/exception/PlayerQuitException.kt | 2 +- .../bukkit/api/schedule/AbstractScheduler.kt | 2 +- .../bukkit/api/schedule/Scheduler.kt | 2 +- .../bukkit/api/schedule/SchedulerTask.kt | 2 +- .../distractic/bukkit/api/world/Cuboid.kt | 94 ++++++++++++++++ .../exception/WorldDifferentException.kt | 2 +- .../world/exception/WorldNotFoundException.kt | 2 +- 43 files changed, 163 insertions(+), 187 deletions(-) delete mode 100644 src/main/kotlin/fr/distractic/bukkit/api/world/BlockPosition.kt delete mode 100644 src/main/kotlin/fr/distractic/bukkit/api/world/Cuboid.kt rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/APIPlugin.kt (76%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/Plugin.kt (63%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/coroutine/exception/SilentCancellationException.kt (87%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/delegate/DelegatePlayer.kt (85%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/delegate/DelegateWorld.kt (85%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/extension/_CommandSender.kt (96%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/extension/_Comparable.kt (67%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/extension/_Component.kt (98%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/extension/_CoroutineScope.kt (96%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/extension/_Duration.kt (96%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/extension/_Event.kt (89%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/extension/_ItemStack.kt (99%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/extension/_JavaPlugin.kt (92%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/extension/_Listener.kt (98%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/extension/_Location.kt (98%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/extension/_MerchantRecipe.kt (97%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/extension/_PersistentDataHolder.kt (91%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/extension/_Player.kt (96%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/extension/_PlayerProfile.kt (94%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/extension/_Runnable.kt (96%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/extension/_String.kt (98%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/extension/_Villager.kt (96%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/extension/_World.kt (97%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/item/CraftBuilder.kt (95%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/item/exception/CraftResultMissingException.kt (75%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/koin/CraftContext.kt (99%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/listener/PlayerListener.kt (82%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/listener/VillagerListener.kt (70%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/player/Client.kt (79%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/player/ClientManager.kt (98%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/player/ClientManagerImpl.kt (94%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/player/exception/ClientAlreadyExistsException.kt (73%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/player/exception/ClientNotFoundException.kt (71%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/player/exception/PlayerNotFoundException.kt (72%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/player/exception/PlayerQuitException.kt (82%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/schedule/AbstractScheduler.kt (95%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/schedule/Scheduler.kt (92%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/schedule/SchedulerTask.kt (99%) create mode 100644 src/main/kotlin/io/github/distractic/bukkit/api/world/Cuboid.kt rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/world/exception/WorldDifferentException.kt (81%) rename src/main/kotlin/{fr => io/github}/distractic/bukkit/api/world/exception/WorldNotFoundException.kt (72%) diff --git a/src/main/kotlin/fr/distractic/bukkit/api/world/BlockPosition.kt b/src/main/kotlin/fr/distractic/bukkit/api/world/BlockPosition.kt deleted file mode 100644 index 8460594b..00000000 --- a/src/main/kotlin/fr/distractic/bukkit/api/world/BlockPosition.kt +++ /dev/null @@ -1,9 +0,0 @@ -package fr.distractic.bukkit.api.world - -/** - * Data class representing a position of one block with x, y, z. - * @property x X value. - * @property y Y value. - * @property z Z value. - */ -public data class BlockPosition(val x: Int, val y: Int, val z: Int) \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/world/Cuboid.kt b/src/main/kotlin/fr/distractic/bukkit/api/world/Cuboid.kt deleted file mode 100644 index 2be930ab..00000000 --- a/src/main/kotlin/fr/distractic/bukkit/api/world/Cuboid.kt +++ /dev/null @@ -1,105 +0,0 @@ -package fr.distractic.bukkit.api.world - -import fr.distractic.bukkit.api.delegate.DelegateWorld -import fr.distractic.bukkit.api.extension.minMax -import fr.distractic.bukkit.api.world.exception.WorldDifferentException -import fr.distractic.bukkit.api.world.exception.WorldNotFoundException -import org.bukkit.Location -import org.bukkit.World -import org.bukkit.block.Block -import java.util.* - -/** - * Represents an area in a minecraft world. - * @property uuidWorld World's UUID. - * @property world World retrieve from the server. - * @property minCoordinate Position with minimal coordinate's value. - * @property maxCoordinate Position with maximal coordinate's value. - */ -public class Cuboid(pluginId: String, public val uuidWorld: UUID, x1: Int, y1: Int, z1: Int, x2: Int, y2: Int, z2: Int) { - - public companion object { - - /** - * Create a cuboid using the position in location instances. - * Throws an exception if the world of both locations are different. - * @param location1 Location 1 - * @param location2 Location 2 - * @return New instance of cuboid. - */ - public fun of(pluginId: String, location1: Location, location2: Location): Cuboid { - val world1 = location1.world - val world2 = location2.world - if (world1 != world2) { - throw WorldDifferentException(world1, world2, "The locations don't have the same world") - } - return Cuboid( - pluginId, - world1.uid, - location1.blockX, - location1.blockY, - location1.blockZ, - location2.blockX, - location2.blockY, - location2.blockZ - ) - } - } - - public val world: World? by DelegateWorld(pluginId, uuidWorld) - - public val minCoordinate: BlockPosition - public val maxCoordinate: BlockPosition - - init { - val (minX, maxX) = minMax(x1, x2) - val (minY, maxY) = minMax(y1, y2) - val (minZ, maxZ) = minMax(z1, z2) - minCoordinate = BlockPosition(minX, minY, minZ) - maxCoordinate = BlockPosition(maxX, maxY, maxZ) - } - - /** - * Retrieve the instance of world. - * If the world is not found from the server, thrown an exception. - * @return The instance of world. - */ - public fun requireWorld(): World = world ?: throw WorldNotFoundException("The world cannot be retrieved from the server") - - /** - * Know if a location is in the area. - * - * @param location Location. - * @return `true` if the location is between bounds, `false` otherwise. - */ - public operator fun contains(location: Location): Boolean = location.world == world - && location.blockX in minCoordinate.x..maxCoordinate.x - && location.blockY in minCoordinate.y..maxCoordinate.y - && location.blockZ in minCoordinate.z..maxCoordinate.z - - /** - * Create an iterator to iterate on all locations present in the area. - * @return Iterator of location. - */ - public fun locationSequence(): Sequence = sequence { - val world = requireWorld() - for (x in minCoordinate.x..maxCoordinate.x) { - val xDouble = x.toDouble() - for (y in minCoordinate.y..maxCoordinate.y) { - val yDouble = y.toDouble() - for (z in minCoordinate.z..maxCoordinate.z) { - yield(Location(world, xDouble, yDouble, z.toDouble())) - } - } - } - } - - /** - * Create an iterator to iterate on all blocks present in the area. - * @return Iterator of location. - */ - public fun blockSequence(): Sequence { - return locationSequence() - .map { it.world.getBlockAt(it) } - } -} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/APIPlugin.kt b/src/main/kotlin/io/github/distractic/bukkit/api/APIPlugin.kt similarity index 76% rename from src/main/kotlin/fr/distractic/bukkit/api/APIPlugin.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/APIPlugin.kt index 55968bc5..76e58eee 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/APIPlugin.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/APIPlugin.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api +package io.github.distractic.bukkit.api import org.bukkit.plugin.java.JavaPlugin diff --git a/src/main/kotlin/fr/distractic/bukkit/api/Plugin.kt b/src/main/kotlin/io/github/distractic/bukkit/api/Plugin.kt similarity index 63% rename from src/main/kotlin/fr/distractic/bukkit/api/Plugin.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/Plugin.kt index 37f33ac9..43d4a13c 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/Plugin.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/Plugin.kt @@ -1,24 +1,23 @@ -package fr.distractic.bukkit.api +package io.github.distractic.bukkit.api import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin -import fr.distractic.bukkit.api.extension.registerListener -import fr.distractic.bukkit.api.koin.CraftContext -import fr.distractic.bukkit.api.koin.loadModule -import fr.distractic.bukkit.api.listener.PlayerListener -import fr.distractic.bukkit.api.listener.VillagerListener -import fr.distractic.bukkit.api.player.Client -import fr.distractic.bukkit.api.player.ClientManager -import fr.distractic.bukkit.api.player.ClientManagerImpl +import io.github.distractic.bukkit.api.extension.registerListener +import io.github.distractic.bukkit.api.koin.CraftContext +import io.github.distractic.bukkit.api.koin.loadModule +import io.github.distractic.bukkit.api.listener.PlayerListener +import io.github.distractic.bukkit.api.listener.VillagerListener +import io.github.distractic.bukkit.api.player.Client +import io.github.distractic.bukkit.api.player.ClientManager +import io.github.distractic.bukkit.api.player.ClientManagerImpl import org.bukkit.Bukkit import org.bukkit.entity.Player -import org.koin.core.component.KoinComponent import org.koin.core.module.Module import org.koin.dsl.bind /** * Abstract plugin with the necessary component to create a plugin. */ -public abstract class Plugin : SuspendingJavaPlugin(), KoinComponent { +public abstract class Plugin : SuspendingJavaPlugin() { public abstract val id: String @@ -29,7 +28,7 @@ public abstract class Plugin : SuspendingJavaPlugin(), KoinComponent { moduleBukkit() registerListener { PlayerListener(this) } - registerListener(::VillagerListener) + registerListener { VillagerListener(this) } } protected open fun modulePlugin(): Module = loadModule(id) { diff --git a/src/main/kotlin/fr/distractic/bukkit/api/coroutine/exception/SilentCancellationException.kt b/src/main/kotlin/io/github/distractic/bukkit/api/coroutine/exception/SilentCancellationException.kt similarity index 87% rename from src/main/kotlin/fr/distractic/bukkit/api/coroutine/exception/SilentCancellationException.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/coroutine/exception/SilentCancellationException.kt index e8ed57fb..4b234cf2 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/coroutine/exception/SilentCancellationException.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/coroutine/exception/SilentCancellationException.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.coroutine.exception +package io.github.distractic.bukkit.api.coroutine.exception import kotlin.coroutines.cancellation.CancellationException diff --git a/src/main/kotlin/fr/distractic/bukkit/api/delegate/DelegatePlayer.kt b/src/main/kotlin/io/github/distractic/bukkit/api/delegate/DelegatePlayer.kt similarity index 85% rename from src/main/kotlin/fr/distractic/bukkit/api/delegate/DelegatePlayer.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/delegate/DelegatePlayer.kt index aacb2931..27ab3c1b 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/delegate/DelegatePlayer.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/delegate/DelegatePlayer.kt @@ -1,6 +1,6 @@ -package fr.distractic.bukkit.api.delegate +package io.github.distractic.bukkit.api.delegate -import fr.distractic.bukkit.api.koin.inject +import io.github.distractic.bukkit.api.koin.inject import org.bukkit.Server import org.bukkit.entity.Player import java.util.* diff --git a/src/main/kotlin/fr/distractic/bukkit/api/delegate/DelegateWorld.kt b/src/main/kotlin/io/github/distractic/bukkit/api/delegate/DelegateWorld.kt similarity index 85% rename from src/main/kotlin/fr/distractic/bukkit/api/delegate/DelegateWorld.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/delegate/DelegateWorld.kt index b787a348..45c8af6f 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/delegate/DelegateWorld.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/delegate/DelegateWorld.kt @@ -1,6 +1,6 @@ -package fr.distractic.bukkit.api.delegate +package io.github.distractic.bukkit.api.delegate -import fr.distractic.bukkit.api.koin.inject +import io.github.distractic.bukkit.api.koin.inject import org.bukkit.Server import org.bukkit.World import java.util.* diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_CommandSender.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_CommandSender.kt similarity index 96% rename from src/main/kotlin/fr/distractic/bukkit/api/extension/_CommandSender.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/extension/_CommandSender.kt index 1a7a7f45..38f812dc 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/extension/_CommandSender.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_CommandSender.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.extension +package io.github.distractic.bukkit.api.extension import net.kyori.adventure.text.format.NamedTextColor import org.bukkit.command.CommandSender diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Comparable.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Comparable.kt similarity index 67% rename from src/main/kotlin/fr/distractic/bukkit/api/extension/_Comparable.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/extension/_Comparable.kt index 11d5282b..3ef1f820 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Comparable.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Comparable.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.extension +package io.github.distractic.bukkit.api.extension /** * Get the lowest and highest value in a specific index. @@ -8,7 +8,7 @@ package fr.distractic.bukkit.api.extension * @param b Second value. * @return Both values with a defined order. */ -public fun minMax(a: T, b: T): Pair where T : Comparable = if (a <= b) { +public fun > minMax(a: T, b: T): Pair = if (a <= b) { a to b } else { b to a diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Component.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Component.kt similarity index 98% rename from src/main/kotlin/fr/distractic/bukkit/api/extension/_Component.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/extension/_Component.kt index c635be25..75ffe09b 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Component.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Component.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.extension +package io.github.distractic.bukkit.api.extension import net.kyori.adventure.text.* import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_CoroutineScope.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_CoroutineScope.kt similarity index 96% rename from src/main/kotlin/fr/distractic/bukkit/api/extension/_CoroutineScope.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/extension/_CoroutineScope.kt index a2095ce1..8dd8266a 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/extension/_CoroutineScope.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_CoroutineScope.kt @@ -1,6 +1,6 @@ -package fr.distractic.bukkit.api.extension +package io.github.distractic.bukkit.api.extension -import fr.distractic.bukkit.api.schedule.SchedulerTask +import io.github.distractic.bukkit.api.schedule.SchedulerTask import kotlinx.coroutines.* import kotlin.coroutines.CoroutineContext import kotlin.coroutines.coroutineContext diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Duration.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Duration.kt similarity index 96% rename from src/main/kotlin/fr/distractic/bukkit/api/extension/_Duration.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/extension/_Duration.kt index b8d80c05..69f68f7a 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Duration.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Duration.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.extension +package io.github.distractic.bukkit.api.extension import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Event.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Event.kt similarity index 89% rename from src/main/kotlin/fr/distractic/bukkit/api/extension/_Event.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/extension/_Event.kt index 886858c1..23493fd6 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Event.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Event.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.extension +package io.github.distractic.bukkit.api.extension import org.bukkit.entity.Damageable import org.bukkit.event.entity.EntityDamageEvent diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_ItemStack.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_ItemStack.kt similarity index 99% rename from src/main/kotlin/fr/distractic/bukkit/api/extension/_ItemStack.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/extension/_ItemStack.kt index 7e605ece..5e2b2705 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/extension/_ItemStack.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_ItemStack.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.extension +package io.github.distractic.bukkit.api.extension import org.bukkit.Material import org.bukkit.inventory.ItemStack diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_JavaPlugin.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_JavaPlugin.kt similarity index 92% rename from src/main/kotlin/fr/distractic/bukkit/api/extension/_JavaPlugin.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/extension/_JavaPlugin.kt index c0440036..2b35e7f6 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/extension/_JavaPlugin.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_JavaPlugin.kt @@ -1,7 +1,7 @@ -package fr.distractic.bukkit.api.extension +package io.github.distractic.bukkit.api.extension import com.github.shynixn.mccoroutine.bukkit.registerSuspendingEvents -import fr.distractic.bukkit.api.item.CraftBuilder +import io.github.distractic.bukkit.api.item.CraftBuilder import org.bukkit.NamespacedKey import org.bukkit.event.Listener import org.bukkit.plugin.java.JavaPlugin diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Listener.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Listener.kt similarity index 98% rename from src/main/kotlin/fr/distractic/bukkit/api/extension/_Listener.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/extension/_Listener.kt index 6d4f36b8..cde11e66 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Listener.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Listener.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.extension +package io.github.distractic.bukkit.api.extension import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeoutOrNull diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Location.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Location.kt similarity index 98% rename from src/main/kotlin/fr/distractic/bukkit/api/extension/_Location.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/extension/_Location.kt index d31e1fb6..17c1cb35 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Location.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Location.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.extension +package io.github.distractic.bukkit.api.extension import org.bukkit.Location import org.bukkit.World diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_MerchantRecipe.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_MerchantRecipe.kt similarity index 97% rename from src/main/kotlin/fr/distractic/bukkit/api/extension/_MerchantRecipe.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/extension/_MerchantRecipe.kt index 24934196..26f4f19a 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/extension/_MerchantRecipe.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_MerchantRecipe.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.extension +package io.github.distractic.bukkit.api.extension import org.bukkit.inventory.ItemStack import org.bukkit.inventory.MerchantRecipe diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_PersistentDataHolder.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_PersistentDataHolder.kt similarity index 91% rename from src/main/kotlin/fr/distractic/bukkit/api/extension/_PersistentDataHolder.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/extension/_PersistentDataHolder.kt index 61fab58c..3072dca9 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/extension/_PersistentDataHolder.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_PersistentDataHolder.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.extension +package io.github.distractic.bukkit.api.extension import org.bukkit.persistence.PersistentDataContainer import org.bukkit.persistence.PersistentDataHolder diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Player.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Player.kt similarity index 96% rename from src/main/kotlin/fr/distractic/bukkit/api/extension/_Player.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/extension/_Player.kt index 00acd217..83af8c7b 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Player.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Player.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.extension +package io.github.distractic.bukkit.api.extension import com.destroystokyo.paper.profile.PlayerProfile import org.bukkit.entity.Player diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_PlayerProfile.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_PlayerProfile.kt similarity index 94% rename from src/main/kotlin/fr/distractic/bukkit/api/extension/_PlayerProfile.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/extension/_PlayerProfile.kt index 0bda0b70..0ece7499 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/extension/_PlayerProfile.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_PlayerProfile.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.extension +package io.github.distractic.bukkit.api.extension import com.destroystokyo.paper.profile.PlayerProfile import com.destroystokyo.paper.profile.ProfileProperty diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Runnable.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Runnable.kt similarity index 96% rename from src/main/kotlin/fr/distractic/bukkit/api/extension/_Runnable.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/extension/_Runnable.kt index ba256ead..8ccb0cb8 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Runnable.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Runnable.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.extension +package io.github.distractic.bukkit.api.extension import com.github.shynixn.mccoroutine.bukkit.asyncDispatcher import com.github.shynixn.mccoroutine.bukkit.minecraftDispatcher diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_String.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_String.kt similarity index 98% rename from src/main/kotlin/fr/distractic/bukkit/api/extension/_String.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/extension/_String.kt index fa45a794..ede7235e 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/extension/_String.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_String.kt @@ -1,7 +1,7 @@ @file:JvmName("StringUtils") @file:JvmMultifileClass -package fr.distractic.bukkit.api.extension +package io.github.distractic.bukkit.api.extension import org.bukkit.ChatColor import java.util.* diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Villager.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Villager.kt similarity index 96% rename from src/main/kotlin/fr/distractic/bukkit/api/extension/_Villager.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/extension/_Villager.kt index e351cf2e..6ee979de 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/extension/_Villager.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Villager.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.extension +package io.github.distractic.bukkit.api.extension import org.bukkit.NamespacedKey import org.bukkit.entity.Villager diff --git a/src/main/kotlin/fr/distractic/bukkit/api/extension/_World.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_World.kt similarity index 97% rename from src/main/kotlin/fr/distractic/bukkit/api/extension/_World.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/extension/_World.kt index 5594ce48..68fa2f05 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/extension/_World.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_World.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.extension +package io.github.distractic.bukkit.api.extension import kotlinx.coroutines.future.await import org.bukkit.Chunk diff --git a/src/main/kotlin/fr/distractic/bukkit/api/item/CraftBuilder.kt b/src/main/kotlin/io/github/distractic/bukkit/api/item/CraftBuilder.kt similarity index 95% rename from src/main/kotlin/fr/distractic/bukkit/api/item/CraftBuilder.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/item/CraftBuilder.kt index 2798c3ed..7e344fc7 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/item/CraftBuilder.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/item/CraftBuilder.kt @@ -1,7 +1,7 @@ -package fr.distractic.bukkit.api.item +package io.github.distractic.bukkit.api.item -import fr.distractic.bukkit.api.extension.item -import fr.distractic.bukkit.api.item.exception.CraftResultMissingException +import io.github.distractic.bukkit.api.extension.item +import io.github.distractic.bukkit.api.item.exception.CraftResultMissingException import org.bukkit.Material import org.bukkit.NamespacedKey import org.bukkit.inventory.ItemStack @@ -72,7 +72,7 @@ public class CraftBuilder { * Storage of the item with the position assigned for the recipe's shape. * The top left position is defined by 0 and bottom right 8 */ - private val craft: Array = arrayOfNulls(9) + private val craft: Array = arrayOfNulls(9) /** * Result item of the craft. diff --git a/src/main/kotlin/fr/distractic/bukkit/api/item/exception/CraftResultMissingException.kt b/src/main/kotlin/io/github/distractic/bukkit/api/item/exception/CraftResultMissingException.kt similarity index 75% rename from src/main/kotlin/fr/distractic/bukkit/api/item/exception/CraftResultMissingException.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/item/exception/CraftResultMissingException.kt index 55e81207..b15f2d3d 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/item/exception/CraftResultMissingException.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/item/exception/CraftResultMissingException.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.item.exception +package io.github.distractic.bukkit.api.item.exception /** * Exception when the result item is not defined. diff --git a/src/main/kotlin/fr/distractic/bukkit/api/koin/CraftContext.kt b/src/main/kotlin/io/github/distractic/bukkit/api/koin/CraftContext.kt similarity index 99% rename from src/main/kotlin/fr/distractic/bukkit/api/koin/CraftContext.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/koin/CraftContext.kt index 53995e95..776bf349 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/koin/CraftContext.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/koin/CraftContext.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.koin +package io.github.distractic.bukkit.api.koin import org.koin.core.Koin import org.koin.core.KoinApplication diff --git a/src/main/kotlin/fr/distractic/bukkit/api/listener/PlayerListener.kt b/src/main/kotlin/io/github/distractic/bukkit/api/listener/PlayerListener.kt similarity index 82% rename from src/main/kotlin/fr/distractic/bukkit/api/listener/PlayerListener.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/listener/PlayerListener.kt index 0766dfeb..9d03f2ef 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/listener/PlayerListener.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/listener/PlayerListener.kt @@ -1,11 +1,11 @@ -package fr.distractic.bukkit.api.listener +package io.github.distractic.bukkit.api.listener -import fr.distractic.bukkit.api.Plugin -import fr.distractic.bukkit.api.coroutine.exception.SilentCancellationException -import fr.distractic.bukkit.api.koin.inject -import fr.distractic.bukkit.api.player.Client -import fr.distractic.bukkit.api.player.ClientManager -import fr.distractic.bukkit.api.player.exception.ClientAlreadyExistsException +import io.github.distractic.bukkit.api.Plugin +import io.github.distractic.bukkit.api.coroutine.exception.SilentCancellationException +import io.github.distractic.bukkit.api.koin.inject +import io.github.distractic.bukkit.api.player.Client +import io.github.distractic.bukkit.api.player.ClientManager +import io.github.distractic.bukkit.api.player.exception.ClientAlreadyExistsException import kotlinx.coroutines.cancel import org.bukkit.entity.Player import org.bukkit.event.EventHandler diff --git a/src/main/kotlin/fr/distractic/bukkit/api/listener/VillagerListener.kt b/src/main/kotlin/io/github/distractic/bukkit/api/listener/VillagerListener.kt similarity index 70% rename from src/main/kotlin/fr/distractic/bukkit/api/listener/VillagerListener.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/listener/VillagerListener.kt index 36c318aa..2041c5db 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/listener/VillagerListener.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/listener/VillagerListener.kt @@ -1,20 +1,17 @@ -package fr.distractic.bukkit.api.listener +package io.github.distractic.bukkit.api.listener -import fr.distractic.bukkit.api.extension.keepProfession +import io.github.distractic.bukkit.api.extension.keepProfession import org.bukkit.event.EventHandler import org.bukkit.event.Listener import org.bukkit.event.entity.VillagerCareerChangeEvent import org.bukkit.plugin.java.JavaPlugin -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject /** * Listener to manage villager state. * @property plugin Plugin. */ -public class VillagerListener : Listener, KoinComponent { +public class VillagerListener(private val plugin: JavaPlugin) : Listener { - private val plugin: JavaPlugin by inject() /** * When a villager will have his profession changed, check if a specific tag is present into the entity. diff --git a/src/main/kotlin/fr/distractic/bukkit/api/player/Client.kt b/src/main/kotlin/io/github/distractic/bukkit/api/player/Client.kt similarity index 79% rename from src/main/kotlin/fr/distractic/bukkit/api/player/Client.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/player/Client.kt index 1b898300..2b5619cf 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/player/Client.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/player/Client.kt @@ -1,7 +1,7 @@ -package fr.distractic.bukkit.api.player +package io.github.distractic.bukkit.api.player -import fr.distractic.bukkit.api.delegate.DelegatePlayer -import fr.distractic.bukkit.api.player.exception.PlayerNotFoundException +import io.github.distractic.bukkit.api.delegate.DelegatePlayer +import io.github.distractic.bukkit.api.player.exception.PlayerNotFoundException import kotlinx.coroutines.CoroutineScope import org.bukkit.entity.Player import java.util.* diff --git a/src/main/kotlin/fr/distractic/bukkit/api/player/ClientManager.kt b/src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManager.kt similarity index 98% rename from src/main/kotlin/fr/distractic/bukkit/api/player/ClientManager.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManager.kt index 6c1318a5..7de3707c 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/player/ClientManager.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManager.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.player +package io.github.distractic.bukkit.api.player import org.bukkit.entity.Player diff --git a/src/main/kotlin/fr/distractic/bukkit/api/player/ClientManagerImpl.kt b/src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImpl.kt similarity index 94% rename from src/main/kotlin/fr/distractic/bukkit/api/player/ClientManagerImpl.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImpl.kt index dabf43d5..8b98ac63 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/player/ClientManagerImpl.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImpl.kt @@ -1,6 +1,6 @@ -package fr.distractic.bukkit.api.player +package io.github.distractic.bukkit.api.player -import fr.distractic.bukkit.api.player.exception.ClientNotFoundException +import io.github.distractic.bukkit.api.player.exception.ClientNotFoundException import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.bukkit.entity.Player diff --git a/src/main/kotlin/fr/distractic/bukkit/api/player/exception/ClientAlreadyExistsException.kt b/src/main/kotlin/io/github/distractic/bukkit/api/player/exception/ClientAlreadyExistsException.kt similarity index 73% rename from src/main/kotlin/fr/distractic/bukkit/api/player/exception/ClientAlreadyExistsException.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/player/exception/ClientAlreadyExistsException.kt index 4c092f1e..59810b3d 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/player/exception/ClientAlreadyExistsException.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/player/exception/ClientAlreadyExistsException.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.player.exception +package io.github.distractic.bukkit.api.player.exception /** * Exception if a client is already exists for a player. diff --git a/src/main/kotlin/fr/distractic/bukkit/api/player/exception/ClientNotFoundException.kt b/src/main/kotlin/io/github/distractic/bukkit/api/player/exception/ClientNotFoundException.kt similarity index 71% rename from src/main/kotlin/fr/distractic/bukkit/api/player/exception/ClientNotFoundException.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/player/exception/ClientNotFoundException.kt index 62f9af61..43e4d830 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/player/exception/ClientNotFoundException.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/player/exception/ClientNotFoundException.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.player.exception +package io.github.distractic.bukkit.api.player.exception /** * Exception if a client cannot be found. diff --git a/src/main/kotlin/fr/distractic/bukkit/api/player/exception/PlayerNotFoundException.kt b/src/main/kotlin/io/github/distractic/bukkit/api/player/exception/PlayerNotFoundException.kt similarity index 72% rename from src/main/kotlin/fr/distractic/bukkit/api/player/exception/PlayerNotFoundException.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/player/exception/PlayerNotFoundException.kt index aaa9182d..92d574b6 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/player/exception/PlayerNotFoundException.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/player/exception/PlayerNotFoundException.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.player.exception +package io.github.distractic.bukkit.api.player.exception /** * Exception if a player is not into the server. diff --git a/src/main/kotlin/fr/distractic/bukkit/api/player/exception/PlayerQuitException.kt b/src/main/kotlin/io/github/distractic/bukkit/api/player/exception/PlayerQuitException.kt similarity index 82% rename from src/main/kotlin/fr/distractic/bukkit/api/player/exception/PlayerQuitException.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/player/exception/PlayerQuitException.kt index ac4ff006..c3979e58 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/player/exception/PlayerQuitException.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/player/exception/PlayerQuitException.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.player.exception +package io.github.distractic.bukkit.api.player.exception import kotlin.coroutines.cancellation.CancellationException diff --git a/src/main/kotlin/fr/distractic/bukkit/api/schedule/AbstractScheduler.kt b/src/main/kotlin/io/github/distractic/bukkit/api/schedule/AbstractScheduler.kt similarity index 95% rename from src/main/kotlin/fr/distractic/bukkit/api/schedule/AbstractScheduler.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/schedule/AbstractScheduler.kt index 43bc7356..59941ee0 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/schedule/AbstractScheduler.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/schedule/AbstractScheduler.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.schedule +package io.github.distractic.bukkit.api.schedule import kotlinx.coroutines.* diff --git a/src/main/kotlin/fr/distractic/bukkit/api/schedule/Scheduler.kt b/src/main/kotlin/io/github/distractic/bukkit/api/schedule/Scheduler.kt similarity index 92% rename from src/main/kotlin/fr/distractic/bukkit/api/schedule/Scheduler.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/schedule/Scheduler.kt index 3c794faf..10f71b3c 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/schedule/Scheduler.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/schedule/Scheduler.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.schedule +package io.github.distractic.bukkit.api.schedule import kotlinx.coroutines.CancellationException diff --git a/src/main/kotlin/fr/distractic/bukkit/api/schedule/SchedulerTask.kt b/src/main/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTask.kt similarity index 99% rename from src/main/kotlin/fr/distractic/bukkit/api/schedule/SchedulerTask.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTask.kt index 675eca1d..0260d317 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/schedule/SchedulerTask.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTask.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.schedule +package io.github.distractic.bukkit.api.schedule import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.coroutineScope diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/world/Cuboid.kt b/src/main/kotlin/io/github/distractic/bukkit/api/world/Cuboid.kt new file mode 100644 index 00000000..2db21bdf --- /dev/null +++ b/src/main/kotlin/io/github/distractic/bukkit/api/world/Cuboid.kt @@ -0,0 +1,94 @@ +package io.github.distractic.bukkit.api.world + +import io.github.distractic.bukkit.api.extension.minMax +import io.github.distractic.bukkit.api.world.exception.WorldDifferentException +import org.bukkit.Bukkit +import org.bukkit.Location +import org.bukkit.block.Block + +/** + * Represents an area in a minecraft world. + * @property startPosition Position with minimal coordinate's value. + * @property endPosition Position with maximal coordinate's value. + */ +public class Cuboid(location1: Location, location2: Location) { + + public companion object { + + /** + * Generate cuboid from a string. + * Each argument is split by space and correspond to a specific data. + * The first argument is the world name. + * The second argument is the x coordinate. + * The third argument is the y coordinate. + * The fourth argument is the z coordinate. + * The fifth argument is the x size. + * The sixth argument is the y size. + * The seventh argument is the z size. + */ + public fun fromString(string: String): Cuboid { + val args = string.split(' ') + val world = Bukkit.getWorld(args[0]) ?: throw IllegalArgumentException("World not found") + val x = args[1].toDouble() + val y = args[2].toDouble() + val z = args[3].toDouble() + val xSize = args[4].toDouble() + val ySize = args[5].toDouble() + val zSize = args[6].toDouble() + return Cuboid(Location(world, x, y, z), Location(world, xSize, ySize, zSize)) + } + } + + public val startPosition: Location + public val endPosition: Location + + init { + val world1 = location1.world + val world2 = location2.world + if (world1 != world2) { + throw WorldDifferentException(world1, world2, "The locations don't have the same world") + } + + val (minX, maxX) = minMax(location1.x, location2.x) + val (minY, maxY) = minMax(location1.y, location2.y) + val (minZ, maxZ) = minMax(location1.z, location2.z) + startPosition = Location(world1, minX, minY, minZ) + endPosition = Location(world1, maxX, maxY, maxZ) + } + + /** + * Know if a location is in the area. + * + * @param location Location. + * @return `true` if the location is between bounds, `false` otherwise. + */ + public operator fun contains(location: Location): Boolean = location.world == startPosition.world + && location.x in startPosition.x..endPosition.x + && location.y in startPosition.y..endPosition.y + && location.z in startPosition.z..endPosition.z + + /** + * Create an iterator to iterate on all locations present in the area. + * @return Iterator of location. + */ + public fun locationSequence(): Sequence = sequence { + val world = startPosition.world + for (x in startPosition.blockX..endPosition.blockX) { + val xDouble = x.toDouble() + for (y in startPosition.blockY..endPosition.blockY) { + val yDouble = y.toDouble() + for (z in startPosition.blockZ..endPosition.blockZ) { + yield(Location(world, xDouble, yDouble, z.toDouble())) + } + } + } + } + + /** + * Create an iterator to iterate on all blocks present in the area. + * @return Iterator of location. + */ + public fun blockSequence(): Sequence { + return locationSequence().map { it.world.getBlockAt(it) } + } +} \ No newline at end of file diff --git a/src/main/kotlin/fr/distractic/bukkit/api/world/exception/WorldDifferentException.kt b/src/main/kotlin/io/github/distractic/bukkit/api/world/exception/WorldDifferentException.kt similarity index 81% rename from src/main/kotlin/fr/distractic/bukkit/api/world/exception/WorldDifferentException.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/world/exception/WorldDifferentException.kt index 1cd57957..87177314 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/world/exception/WorldDifferentException.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/world/exception/WorldDifferentException.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.world.exception +package io.github.distractic.bukkit.api.world.exception import org.bukkit.World diff --git a/src/main/kotlin/fr/distractic/bukkit/api/world/exception/WorldNotFoundException.kt b/src/main/kotlin/io/github/distractic/bukkit/api/world/exception/WorldNotFoundException.kt similarity index 72% rename from src/main/kotlin/fr/distractic/bukkit/api/world/exception/WorldNotFoundException.kt rename to src/main/kotlin/io/github/distractic/bukkit/api/world/exception/WorldNotFoundException.kt index 85077da1..57c626c6 100644 --- a/src/main/kotlin/fr/distractic/bukkit/api/world/exception/WorldNotFoundException.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/world/exception/WorldNotFoundException.kt @@ -1,4 +1,4 @@ -package fr.distractic.bukkit.api.world.exception +package io.github.distractic.bukkit.api.world.exception /** * Exception if a world is not into the server. From a60218eff0496a358736102332dbef58eb72e9a6 Mon Sep 17 00:00:00 2001 From: Distractic Date: Fri, 19 Aug 2022 18:46:36 +0200 Subject: [PATCH 021/143] chore: Upgrade dependencies --- build.gradle.kts | 38 +++++++++++++++++++++++--------------- gradle.properties | 13 +++++++------ 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index a8027719..865d59ea 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,31 +14,39 @@ repositories { } } +val coroutineVersion: String by project +val loggingVersion: String by project +val koinVersion: String by project +val mccoroutineVersion: String by project +val paperVersion: String by project +val junitVersion: String by project +val mockkVersion: String by project +val slf4jVersion: String by project + dependencies { implementation(kotlin("stdlib")) implementation(kotlin("reflect")) - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.6.0") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - implementation("io.github.microutils:kotlin-logging:2.1.21") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutineVersion") + implementation("io.github.microutils:kotlin-logging:$loggingVersion") - implementation("io.insert-koin:koin-core:3.2.0") - implementation("io.insert-koin:koin-logger-slf4j:3.2.0") + implementation("io.insert-koin:koin-core:$koinVersion") + implementation("io.insert-koin:koin-logger-slf4j:$koinVersion") - implementation("com.github.shynixn.mccoroutine:mccoroutine-bukkit-api:2.2.0") - implementation("com.github.shynixn.mccoroutine:mccoroutine-bukkit-core:2.2.0") + implementation("com.github.shynixn.mccoroutine:mccoroutine-bukkit-api:$mccoroutineVersion") + implementation("com.github.shynixn.mccoroutine:mccoroutine-bukkit-core:$mccoroutineVersion") - compileOnly("io.papermc.paper:paper-api:1.19-R0.1-SNAPSHOT") + compileOnly("io.papermc.paper:paper-api:$paperVersion") testImplementation(kotlin("test-junit5")) - testImplementation("org.junit.jupiter:junit-jupiter-params:5.8.2") - testImplementation("io.insert-koin:koin-test:3.2.0") { + testImplementation("org.junit.jupiter:junit-jupiter-params:$junitVersion") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitVersion") + testImplementation("io.insert-koin:koin-test:$koinVersion") { exclude("org.jetbrains.kotlin", "kotlin-test-junit") } - testImplementation("io.mockk:mockk:1.12.2") - testImplementation("org.slf4j:slf4j-api:2.0.0-alpha6") - testImplementation("org.slf4j:slf4j-simple:2.0.0-alpha6") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") + testImplementation("io.mockk:mockk:$mockkVersion") + testImplementation("org.slf4j:slf4j-api:$slf4jVersion") + testImplementation("org.slf4j:slf4j-simple:$slf4jVersion") } kotlin { diff --git a/gradle.properties b/gradle.properties index 44dc6b35..75479481 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,10 +5,11 @@ description= org.gradle.parallel = true kotlin.incremental = true +coroutineVersion=1.6.4 +loggingVersion=2.1.23 +koinVersion=3.2.0 +mccoroutineVersion=2.4.0 paperVersion=1.19-R0.1-SNAPSHOT -viaVersionApiVersion=4.4.1 -rushyCoreVersion=1.0.0 -dataServiceVersion=9c936e59b8 -lombokVersion=1.18.24 -jupiterVersion=5.9.0 -mockitoVersion=4.7.0 \ No newline at end of file +junitVersion=5.9.0 +mockkVersion=1.9.3 +slf4jVersion=2.0.0-alpha6 \ No newline at end of file From 426b8848e71276a199cc1ddab517eff57af22251 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 20 Aug 2022 08:00:57 +0200 Subject: [PATCH 022/143] fix: Publish maven dependsOn --- build.gradle.kts | 10 ++++++++-- gradle.properties | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 865d59ea..39600744 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -82,7 +82,6 @@ tasks { } dokkaHtml.configure { - dependsOn(clean) outputDirectory.set(file(dokkaOutputDir)) } @@ -95,6 +94,11 @@ val deleteDokkaOutputDir by tasks.register("deleteDokkaOutputDirectory") delete(dokkaOutputDir) } +val sourcesJar by tasks.registering(Jar::class) { + archiveClassifier.set("sources") + from(sourceSets.main.get().allSource) +} + val javadocJar = tasks.register("javadocJar") { dependsOn(deleteDokkaOutputDir, tasks.dokkaHtml) archiveClassifier.set("javadoc") @@ -107,7 +111,9 @@ publishing { val projectGitUrl = "https://github.com/$projectOrganizationPath" create(project.name) { - artifact(javadocJar) + from(components["kotlin"]) + artifact(javadocJar.get()) + pom { name.set(project.name) description.set(project.description) diff --git a/gradle.properties b/gradle.properties index 75479481..06790684 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,5 +11,5 @@ koinVersion=3.2.0 mccoroutineVersion=2.4.0 paperVersion=1.19-R0.1-SNAPSHOT junitVersion=5.9.0 -mockkVersion=1.9.3 +mockkVersion=1.12.5 slf4jVersion=2.0.0-alpha6 \ No newline at end of file From c0743d78015773f9aef2274b6cd732c4c4990db3 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 20 Aug 2022 08:04:04 +0200 Subject: [PATCH 023/143] chore: Add information in player left event --- .../io/github/distractic/bukkit/api/listener/PlayerListener.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/listener/PlayerListener.kt b/src/main/kotlin/io/github/distractic/bukkit/api/listener/PlayerListener.kt index 9d03f2ef..18ca05e8 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/listener/PlayerListener.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/listener/PlayerListener.kt @@ -57,6 +57,6 @@ public class PlayerListener(private val plugin: Plugin) : Listener { public suspend fun onQuit(event: PlayerQuitEvent) { val player = event.player val client = clients.removeClient(player) ?: return - client.cancel(SilentCancellationException("The player ${player.uniqueId} left")) + client.cancel(SilentCancellationException("The player ${player.name} (${player.uniqueId}) left")) } } \ No newline at end of file From 955d4b3b1fd54f70cee6d8ea985409b0d4e4ef1c Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 20 Aug 2022 13:26:11 +0200 Subject: [PATCH 024/143] chore: Add config plugin files --- src/main/resources/config.yml | 0 src/main/resources/plugin.yml | 5 +++++ src/main/resources/simplelogger.properties | 2 ++ 3 files changed, 7 insertions(+) create mode 100644 src/main/resources/config.yml create mode 100644 src/main/resources/plugin.yml create mode 100644 src/main/resources/simplelogger.properties diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 00000000..e69de29b diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 00000000..c50d1e8e --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,5 @@ +name: bukkit-api +version: 1.0 +author: Distractic +api-version: 1.19 +main: io.github.distractic.bukkit.api.APIPlugin \ No newline at end of file diff --git a/src/main/resources/simplelogger.properties b/src/main/resources/simplelogger.properties new file mode 100644 index 00000000..d4ce890b --- /dev/null +++ b/src/main/resources/simplelogger.properties @@ -0,0 +1,2 @@ +org.slf4j.simpleLogger.defaultLogLevel=debug +org.slf4j.simpleLogger.showDateTime=true \ No newline at end of file From 04bc617bffe51a240ecd2fa1123af0a98f59175f Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 20 Aug 2022 13:50:43 +0200 Subject: [PATCH 025/143] fix: Build gradle to publish in local --- build.gradle.kts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 39600744..3396854f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -86,7 +86,7 @@ tasks { } shadowJar { - archiveFileName.set("${project.name}.jar") + archiveClassifier.set("") } } @@ -94,11 +94,6 @@ val deleteDokkaOutputDir by tasks.register("deleteDokkaOutputDirectory") delete(dokkaOutputDir) } -val sourcesJar by tasks.registering(Jar::class) { - archiveClassifier.set("sources") - from(sourceSets.main.get().allSource) -} - val javadocJar = tasks.register("javadocJar") { dependsOn(deleteDokkaOutputDir, tasks.dokkaHtml) archiveClassifier.set("javadoc") @@ -111,7 +106,7 @@ publishing { val projectGitUrl = "https://github.com/$projectOrganizationPath" create(project.name) { - from(components["kotlin"]) + shadow.component(this) artifact(javadocJar.get()) pom { From f08b814183dad8f246dc2dfd36268a041f8e81ef Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 20 Aug 2022 13:59:30 +0200 Subject: [PATCH 026/143] fix: Compile Kotlin to Java17 --- build.gradle.kts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 3396854f..e6bca35d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -51,9 +51,6 @@ dependencies { kotlin { explicitApi = org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode.Strict - jvmToolchain { - languageVersion.set(JavaLanguageVersion.of(17)) - } sourceSets { all { @@ -69,6 +66,10 @@ kotlin { val dokkaOutputDir = "${rootProject.projectDir}/dokka" tasks { + withType { + kotlinOptions.jvmTarget = JavaVersion.VERSION_17.toString() + } + test { useJUnitPlatform() } From b33210ffb1babfcd614c9ef5696aeda3cdc59ec0 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 20 Aug 2022 14:08:31 +0200 Subject: [PATCH 027/143] fix: Add sources jar in publish --- build.gradle.kts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index e6bca35d..b2543849 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -95,6 +95,11 @@ val deleteDokkaOutputDir by tasks.register("deleteDokkaOutputDirectory") delete(dokkaOutputDir) } +val sourcesJar by tasks.registering(Jar::class) { + archiveClassifier.set("sources") + from(sourceSets.main.get().allSource) +} + val javadocJar = tasks.register("javadocJar") { dependsOn(deleteDokkaOutputDir, tasks.dokkaHtml) archiveClassifier.set("javadoc") @@ -108,6 +113,7 @@ publishing { create(project.name) { shadow.component(this) + artifact(sourcesJar.get()) artifact(javadocJar.get()) pom { From ac83169007ad82c9a724727633e58d56e563506e Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 20 Aug 2022 14:09:09 +0200 Subject: [PATCH 028/143] fix: Remove abuse use of lock in client manager --- .../bukkit/api/player/ClientManagerImpl.kt | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImpl.kt b/src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImpl.kt index 8b98ac63..590fecba 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImpl.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImpl.kt @@ -39,18 +39,12 @@ public class ClientManagerImpl : ClientManager { _clients.remove(getKey(player)) } - override suspend fun getClient(player: Player): Client = mutex.withLock { - getClient(getKey(player)) - } + override suspend fun getClient(player: Player): Client = getClient(getKey(player)) override suspend fun getClient(key: String): Client = - mutex.withLock { getClientOrNull(key) ?: throw ClientNotFoundException("No client is linked to the name [$key]") - } - override suspend fun getClientOrNull(player: Player): Client? = mutex.withLock { - getClientOrNull(getKey(player)) - } + override suspend fun getClientOrNull(player: Player): Client? = getClientOrNull(getKey(player)) override suspend fun getClientOrNull(key: String): Client? = mutex.withLock { _clients[key] @@ -61,9 +55,7 @@ public class ClientManagerImpl : ClientManager { * @param p Player that has the key * @return The key for the Map */ - private suspend fun getKey(p: Player): String = mutex.withLock { - p.name - } + private fun getKey(p: Player): String = p.name /** * Check if a client is linked to a player. From 53abafbb3f7a84ff90dfc33295c336424319bb85 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 20 Aug 2022 14:24:45 +0200 Subject: [PATCH 029/143] fix: Set suspend function for change thread --- .../io/github/distractic/bukkit/api/extension/_Runnable.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Runnable.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Runnable.kt index 8ccb0cb8..a528e9c9 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Runnable.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Runnable.kt @@ -28,7 +28,7 @@ public inline fun BukkitRunnable(crossinline task: BukkitRunnable.() -> Unit): B */ public suspend inline fun onPrimaryThread( plugin: Plugin, - noinline block: CoroutineScope.() -> T + noinline block: suspend CoroutineScope.() -> T ): T = withContext(plugin.minecraftDispatcher, block) /** @@ -39,5 +39,5 @@ public suspend inline fun onPrimaryThread( */ public suspend inline fun onAsyncThread( plugin: Plugin, - noinline block: CoroutineScope.() -> T + noinline block: suspend CoroutineScope.() -> T ): T = withContext(plugin.asyncDispatcher, block) \ No newline at end of file From 869f7fe3f99338bacd54b0b2df0c459ffa544cd6 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 20 Aug 2022 14:25:10 +0200 Subject: [PATCH 030/143] chore: Add possibility to register custom plugin --- src/main/kotlin/io/github/distractic/bukkit/api/Plugin.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/Plugin.kt b/src/main/kotlin/io/github/distractic/bukkit/api/Plugin.kt index 43d4a13c..62667feb 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/Plugin.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/Plugin.kt @@ -31,8 +31,12 @@ public abstract class Plugin : SuspendingJavaPlugin() { registerListener { VillagerListener(this) } } - protected open fun modulePlugin(): Module = loadModule(id) { + protected inline fun modulePlugin(): Module = loadModule(id) { single { this@Plugin } + single { this@Plugin as T } + } + + protected fun moduleClients(): Module = loadModule(id) { single { ClientManagerImpl() } bind ClientManager::class } From b0b52313eecede334d4761878fb7cfb99debd368 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 20 Aug 2022 14:27:11 +0200 Subject: [PATCH 031/143] chore: format --- .../io/github/distractic/bukkit/api/Plugin.kt | 2 +- .../bukkit/api/extension/_CommandSender.kt | 6 ++++-- .../distractic/bukkit/api/extension/_Comparable.kt | 2 +- .../bukkit/api/extension/_CoroutineScope.kt | 9 +++++++-- .../bukkit/api/extension/_PlayerProfile.kt | 3 ++- .../github/distractic/bukkit/api/player/Client.kt | 6 ++++-- .../distractic/bukkit/api/player/ClientManager.kt | 3 ++- .../bukkit/api/player/ClientManagerImpl.kt | 2 +- .../distractic/bukkit/api/schedule/SchedulerTask.kt | 13 +++++++++---- .../api/world/exception/WorldDifferentException.kt | 6 +++++- 10 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/Plugin.kt b/src/main/kotlin/io/github/distractic/bukkit/api/Plugin.kt index 62667feb..a1d59c4c 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/Plugin.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/Plugin.kt @@ -31,7 +31,7 @@ public abstract class Plugin : SuspendingJavaPlugin() { registerListener { VillagerListener(this) } } - protected inline fun modulePlugin(): Module = loadModule(id) { + protected inline fun modulePlugin(): Module = loadModule(id) { single { this@Plugin } single { this@Plugin as T } } diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_CommandSender.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_CommandSender.kt index 38f812dc..210b55ca 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_CommandSender.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_CommandSender.kt @@ -21,7 +21,8 @@ public fun CommandSender.sendMessageError(message: String): Unit = sendMessage(t * @return `true` if the sender has all permission, `false` otherwise. */ @Suppress("NOTHING_TO_INLINE") -public inline fun CommandSender.hasPermissions(vararg permissions: String): Boolean = permissions.all(this::hasPermission) +public inline fun CommandSender.hasPermissions(vararg permissions: String): Boolean = + permissions.all(this::hasPermission) /** * Verify if a sender has several permissions. @@ -31,4 +32,5 @@ public inline fun CommandSender.hasPermissions(vararg permissions: String): Bool * @return `true` if the sender has all permission, `false` otherwise. */ @Suppress("NOTHING_TO_INLINE") -public inline fun CommandSender.hasPermissions(permissions: Iterable): Boolean = permissions.all(this::hasPermission) \ No newline at end of file +public inline fun CommandSender.hasPermissions(permissions: Iterable): Boolean = + permissions.all(this::hasPermission) \ No newline at end of file diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Comparable.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Comparable.kt index 3ef1f820..b955072d 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Comparable.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Comparable.kt @@ -8,7 +8,7 @@ package io.github.distractic.bukkit.api.extension * @param b Second value. * @return Both values with a defined order. */ -public fun > minMax(a: T, b: T): Pair = if (a <= b) { +public fun > minMax(a: T, b: T): Pair = if (a <= b) { a to b } else { b to a diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_CoroutineScope.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_CoroutineScope.kt index 8dd8266a..0e9bf148 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_CoroutineScope.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_CoroutineScope.kt @@ -1,7 +1,9 @@ package io.github.distractic.bukkit.api.extension import io.github.distractic.bukkit.api.schedule.SchedulerTask -import kotlinx.coroutines.* +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.withContext import kotlin.coroutines.CoroutineContext import kotlin.coroutines.coroutineContext import kotlin.time.Duration @@ -27,7 +29,10 @@ import kotlin.time.Duration * For example, when using `withContext(NonCancellable) { ... }` there is no change in dispatcher and * this call will not be cancelled neither on entry to the block inside `withContext` nor on exit from it. */ -public suspend inline fun withScopeContext(scope: CoroutineScope, noinline block: suspend CoroutineScope.() -> T): T { +public suspend inline fun withScopeContext( + scope: CoroutineScope, + noinline block: suspend CoroutineScope.() -> T +): T { return withContext(scope.coroutineContext, block) } diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_PlayerProfile.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_PlayerProfile.kt index 0ece7499..5446b8de 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_PlayerProfile.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_PlayerProfile.kt @@ -25,4 +25,5 @@ public inline fun PlayerProfile.setTextures(skin: String, signature: String? = n * @return The property with the name, `null` otherwise. */ @Suppress("NOTHING_TO_INLINE") -public inline fun PlayerProfile.getTexturesProperty(): ProfileProperty? = properties.find { it.name == PROPERTY_TEXTURES } \ No newline at end of file +public inline fun PlayerProfile.getTexturesProperty(): ProfileProperty? = + properties.find { it.name == PROPERTY_TEXTURES } \ No newline at end of file diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/player/Client.kt b/src/main/kotlin/io/github/distractic/bukkit/api/player/Client.kt index 2b5619cf..9d6f233e 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/player/Client.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/player/Client.kt @@ -11,7 +11,8 @@ import java.util.* * @property playerUUID Player's uuid. * @property player Player linked to the client. */ -public open class Client(pluginId: String, public val playerUUID: UUID, coroutineScope: CoroutineScope) : CoroutineScope by coroutineScope { +public open class Client(pluginId: String, public val playerUUID: UUID, coroutineScope: CoroutineScope) : + CoroutineScope by coroutineScope { public val player: Player? by DelegatePlayer(pluginId, playerUUID) @@ -20,5 +21,6 @@ public open class Client(pluginId: String, public val playerUUID: UUID, coroutin * If the player is not found from the server, thrown an exception. * @return The instance of player. */ - public fun requirePlayer(): Player = player ?: throw PlayerNotFoundException("The player cannot be retrieved from the server") + public fun requirePlayer(): Player = + player ?: throw PlayerNotFoundException("The player cannot be retrieved from the server") } \ No newline at end of file diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManager.kt b/src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManager.kt index 7de3707c..e85cd350 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManager.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManager.kt @@ -29,7 +29,8 @@ public suspend inline fun ClientManager.getTypedClientOrNul * @param key Key to find a client. * @return The client linked to a player, `null` if not found. */ -public suspend inline fun ClientManager.getTypedClientOrNull(key: String): T? = getClientOrNull(key) as T? +public suspend inline fun ClientManager.getTypedClientOrNull(key: String): T? = + getClientOrNull(key) as T? /** * Manage the existing client present in the server. diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImpl.kt b/src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImpl.kt index 590fecba..a6a93968 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImpl.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImpl.kt @@ -42,7 +42,7 @@ public class ClientManagerImpl : ClientManager { override suspend fun getClient(player: Player): Client = getClient(getKey(player)) override suspend fun getClient(key: String): Client = - getClientOrNull(key) ?: throw ClientNotFoundException("No client is linked to the name [$key]") + getClientOrNull(key) ?: throw ClientNotFoundException("No client is linked to the name [$key]") override suspend fun getClientOrNull(player: Player): Client? = getClientOrNull(getKey(player)) diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTask.kt b/src/main/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTask.kt index 0260d317..54d28b74 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTask.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTask.kt @@ -114,9 +114,10 @@ public class SchedulerTask( * @param body Lambda function. * @return Task created. */ - public suspend fun add(id: String = UUID.randomUUID().toString(), body: suspend Task.() -> Unit): Task = mutex.withLock { - addUnsafe(id, body) - } + public suspend fun add(id: String = UUID.randomUUID().toString(), body: suspend Task.() -> Unit): Task = + mutex.withLock { + addUnsafe(id, body) + } /** * Add a new body should be executed in the scheduler. @@ -139,7 +140,11 @@ public class SchedulerTask( * @param body Lambda function. * @return Task created. */ - public suspend fun addAt(index: Int, id: String = UUID.randomUUID().toString(), body: suspend Task.() -> Unit): Task = + public suspend fun addAt( + index: Int, + id: String = UUID.randomUUID().toString(), + body: suspend Task.() -> Unit + ): Task = mutex.withLock { addAtUnsafe(index, id, body) } /** diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/world/exception/WorldDifferentException.kt b/src/main/kotlin/io/github/distractic/bukkit/api/world/exception/WorldDifferentException.kt index 87177314..fe54cb81 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/world/exception/WorldDifferentException.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/world/exception/WorldDifferentException.kt @@ -5,5 +5,9 @@ import org.bukkit.World /** * Exception when two worlds are different. */ -public class WorldDifferentException(public val world1: World? = null, public val world2: World? = null, message: String? = null) : +public class WorldDifferentException( + public val world1: World? = null, + public val world2: World? = null, + message: String? = null +) : RuntimeException(message) \ No newline at end of file From 58694d004fdc69611e5cf4656f8e775860c1da42 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 20 Aug 2022 14:34:22 +0200 Subject: [PATCH 032/143] fix: Optimization string to uuid --- .../bukkit/api/extension/_String.kt | 41 ++----------------- 1 file changed, 3 insertions(+), 38 deletions(-) diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_String.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_String.kt index ede7235e..682773e8 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_String.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_String.kt @@ -4,6 +4,7 @@ package io.github.distractic.bukkit.api.extension import org.bukkit.ChatColor +import java.math.BigInteger import java.util.* /** @@ -11,33 +12,6 @@ import java.util.* */ public const val UUID_SIZE: Int = 36 -/** - * First index of the dash. - */ -public const val UUID_INDEX_FIRST_DASH: Int = 8 - -/** - * Second index of the dash. - */ -public const val UUID_INDEX_SECOND_DASH: Int = 13 - -/** - * Third index of the dash. - */ -public const val UUID_INDEX_THIRD_DASH: Int = 18 - -/** - * Fourth index of the dash. - */ -public const val UUID_INDEX_FOURTH_DASH: Int = 23 - -/** - * Array containing all indexes of dashes. - * @return Array with all indexes. - */ -private fun uuidDashIndexes(): Array = - arrayOf(UUID_INDEX_FIRST_DASH, UUID_INDEX_SECOND_DASH, UUID_INDEX_THIRD_DASH, UUID_INDEX_FOURTH_DASH) - /** * Apply the coloration of Bukkit * @see ChatColor.translateAlternateColorCodes @@ -125,15 +99,6 @@ public fun String.toUUID(): UUID { return toUUIDStrict() } - val dashIndexes = uuidDashIndexes() - // Without dash - if (length == UUID_SIZE - dashIndexes.size) { - return StringBuilder(this).apply { - for (dashIndex in dashIndexes) { - insert(dashIndex, '-') - } - }.toString().toUUIDStrict() - } - - throw IllegalArgumentException("Invalid UUID string: $this") + val idHex = BigInteger(this, 16) + return UUID(idHex.shiftRight(64).toLong(), idHex.toLong()) } \ No newline at end of file From cae3f69c50a933ae6bb598bb25b13c99281e8990 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 20 Aug 2022 14:38:41 +0200 Subject: [PATCH 033/143] fix: Remove spread operator --- .../distractic/bukkit/api/extension/_CommandSender.kt | 2 +- .../github/distractic/bukkit/api/item/CraftBuilder.kt | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_CommandSender.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_CommandSender.kt index 210b55ca..084b5cda 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_CommandSender.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_CommandSender.kt @@ -21,7 +21,7 @@ public fun CommandSender.sendMessageError(message: String): Unit = sendMessage(t * @return `true` if the sender has all permission, `false` otherwise. */ @Suppress("NOTHING_TO_INLINE") -public inline fun CommandSender.hasPermissions(vararg permissions: String): Boolean = +public inline fun CommandSender.hasPermissions(permissions: Array): Boolean = permissions.all(this::hasPermission) /** diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/item/CraftBuilder.kt b/src/main/kotlin/io/github/distractic/bukkit/api/item/CraftBuilder.kt index 7e344fc7..aa8ba2e7 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/item/CraftBuilder.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/item/CraftBuilder.kt @@ -85,9 +85,9 @@ public class CraftBuilder { * @param material Item type. * @return The item created from the material. */ - public fun set(vararg positions: CraftSlot, material: Material): ItemStack { + public fun set(positions: Array, material: Material): ItemStack { return ItemStack(material).also { - set(*positions, item = it) + set(positions, item = it) } } @@ -97,10 +97,10 @@ public class CraftBuilder { * @param builder Item builder. * @return The item built from the builder. */ - public inline fun set(vararg positions: CraftSlot, builder: ItemStack.() -> Unit): ItemStack { + public inline fun set(positions: Array, builder: ItemStack.() -> Unit): ItemStack { contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } return item(builder).also { - set(*positions, item = it) + set(positions, item = it) } } @@ -109,7 +109,7 @@ public class CraftBuilder { * @param positions Positions of the item. * @param item Item. */ - public fun set(vararg positions: CraftSlot, item: ItemStack) { + public fun set(positions: Array, item: ItemStack) { positions.forEach { craft[it.index.toInt()] = item } From f0b8a1b9976b8755e751ef21f8591f1aff390142 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Aug 2022 12:46:29 +0000 Subject: [PATCH 034/143] chore(deps): bump gradle/gradle-build-action from 2.2.2 to 2.2.5 Bumps [gradle/gradle-build-action](https://github.com/gradle/gradle-build-action) from 2.2.2 to 2.2.5. - [Release notes](https://github.com/gradle/gradle-build-action/releases) - [Commits](https://github.com/gradle/gradle-build-action/compare/v2.2.2...v2.2.5) --- updated-dependencies: - dependency-name: gradle/gradle-build-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/Check.yml | 4 ++-- .github/workflows/CheckDependabot.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/Check.yml b/.github/workflows/Check.yml index dfa67b74..a1b19aaa 100644 --- a/.github/workflows/Check.yml +++ b/.github/workflows/Check.yml @@ -24,11 +24,11 @@ jobs: jdk: 17 - name: Build - uses: gradle/gradle-build-action@v2.2.2 + uses: gradle/gradle-build-action@v2.2.5 with: arguments: build -x test - name: Test - uses: gradle/gradle-build-action@v2.2.2 + uses: gradle/gradle-build-action@v2.2.5 with: arguments: test \ No newline at end of file diff --git a/.github/workflows/CheckDependabot.yml b/.github/workflows/CheckDependabot.yml index 6845362d..f58c94dd 100644 --- a/.github/workflows/CheckDependabot.yml +++ b/.github/workflows/CheckDependabot.yml @@ -18,11 +18,11 @@ jobs: jdk: 17 - name: Build - uses: gradle/gradle-build-action@v2.2.2 + uses: gradle/gradle-build-action@v2.2.5 with: arguments: build -x test - name: Test - uses: gradle/gradle-build-action@v2.2.2 + uses: gradle/gradle-build-action@v2.2.5 with: arguments: test \ No newline at end of file From 02994cbae12c370ddf5443efa43bda69704eced0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Aug 2022 07:33:41 +0000 Subject: [PATCH 035/143] chore(deps): bump net.researchgate.release from 3.0.0 to 3.0.1 Bumps net.researchgate.release from 3.0.0 to 3.0.1. --- updated-dependencies: - dependency-name: net.researchgate.release dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index b2543849..357c8242 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ plugins { kotlin("plugin.serialization") version "1.7.10" id("org.jetbrains.dokka") version "1.7.10" id("com.github.johnrengelman.shadow") version "7.1.2" - id("net.researchgate.release") version "3.0.0" + id("net.researchgate.release") version "3.0.1" `maven-publish` } From 029ebcbcdc2df14e5ccdba9cc918f93702e0f3d9 Mon Sep 17 00:00:00 2001 From: Distractic <46402441+Distractic@users.noreply.github.com> Date: Sat, 27 Aug 2022 21:55:22 +0200 Subject: [PATCH 036/143] tests: Add tests linked to the #1 PR (#3) --- build.gradle.kts | 3 + .../io/github/distractic/bukkit/api/Plugin.kt | 2 +- .../bukkit/api/extension/_String.kt | 8 +- .../bukkit/api/item/CraftBuilder.kt | 11 +- .../bukkit/api/koin/CraftContext.kt | 24 +- .../bukkit/api/schedule/SchedulerTask.kt | 6 +- .../distractic/bukkit/api/world/Cuboid.kt | 37 +- .../distractic/bukkit/api/AbstractKoinTest.kt | 51 ++ .../bukkit/api/delegate/DelegateWorldTest.kt | 41 ++ .../bukkit/api/delegate/PlayerWorldTest.kt | 41 ++ .../api/extension/CommandSenderExtTest.kt | 37 ++ .../bukkit/api/extension/ComparableExtTest.kt | 53 ++ .../bukkit/api/extension/CoroutineScopeExt.kt | 57 +++ .../bukkit/api/extension/DurationExtTest.kt | 114 +++++ .../bukkit/api/extension/EventExtTest.kt | 50 ++ .../bukkit/api/extension/ItemStackExtTest.kt | 326 +++++++++++++ .../bukkit/api/extension/JavaPluginExtTest.kt | 168 +++++++ .../bukkit/api/extension/LocationExtTest.kt | 142 ++++++ .../api/extension/MerchantRecipeExtTest.kt | 86 ++++ .../api/extension/PersistentDataHolderTest.kt | 23 + .../bukkit/api/extension/PlayerExtTest.kt | 103 ++++ .../bukkit/api/extension/RunnableExtTest.kt | 17 + .../bukkit/api/extension/StringExtTest.kt | 128 +++++ .../bukkit/api/extension/VillagerExtTest.kt | 88 ++++ .../bukkit/api/extension/WorldExtTest.kt | 95 ++++ .../bukkit/api/koin/CraftContextTest.kt | 341 +++++++++++++ .../bukkit/api/listener/PlayerListenerTest.kt | 131 +++++ .../api/listener/VillagerListenerTest.kt | 73 +++ .../api/player/ClientManagerImplTest.kt | 173 +++++++ .../bukkit/api/player/ClientTest.kt | 52 ++ .../bukkit/api/schedule/SchedulerTaskTest.kt | 456 ++++++++++++++++++ .../distractic/bukkit/api/utils/Generator.kt | 23 + .../bukkit/api/utils/LocationTestUtils.kt | 15 + .../distractic/bukkit/api/world/CuboidTest.kt | 392 +++++++++++++++ 34 files changed, 3335 insertions(+), 32 deletions(-) create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/AbstractKoinTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/delegate/DelegateWorldTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/delegate/PlayerWorldTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/extension/CommandSenderExtTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/extension/ComparableExtTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/extension/CoroutineScopeExt.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/extension/DurationExtTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/extension/EventExtTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/extension/ItemStackExtTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/extension/JavaPluginExtTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/extension/LocationExtTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/extension/MerchantRecipeExtTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/extension/PersistentDataHolderTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/extension/PlayerExtTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/extension/RunnableExtTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/extension/StringExtTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/extension/VillagerExtTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/extension/WorldExtTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/koin/CraftContextTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/listener/PlayerListenerTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/listener/VillagerListenerTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImplTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/player/ClientTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTaskTest.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/utils/Generator.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/utils/LocationTestUtils.kt create mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/world/CuboidTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 357c8242..3cd0e45b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,6 +39,8 @@ dependencies { compileOnly("io.papermc.paper:paper-api:$paperVersion") testImplementation(kotlin("test-junit5")) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutineVersion") + testImplementation("io.papermc.paper:paper-api:$paperVersion") testImplementation("org.junit.jupiter:junit-jupiter-params:$junitVersion") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitVersion") testImplementation("io.insert-koin:koin-test:$koinVersion") { @@ -58,6 +60,7 @@ kotlin { optIn("kotlin.RequiresOptIn") optIn("kotlin.ExperimentalStdlibApi") optIn("kotlin.contracts.ExperimentalContracts") + optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") } } } diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/Plugin.kt b/src/main/kotlin/io/github/distractic/bukkit/api/Plugin.kt index a1d59c4c..2e139bc9 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/Plugin.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/Plugin.kt @@ -24,7 +24,7 @@ public abstract class Plugin : SuspendingJavaPlugin() { override suspend fun onEnableAsync() { super.onEnableAsync() - CraftContext.startKoin(id) { } + CraftContext.startKoin(id) moduleBukkit() registerListener { PlayerListener(this) } diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_String.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_String.kt index 682773e8..50ab4a15 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_String.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_String.kt @@ -95,10 +95,12 @@ public fun String.toUUIDOrNull(): UUID? = try { */ @Throws(IllegalArgumentException::class) public fun String.toUUID(): UUID { + val length = this.length if (length == UUID_SIZE) { return toUUIDStrict() + } else if (length == UUID_SIZE - 4) { // -4 because of dashes + val idHex = BigInteger(this, 16) + return UUID(idHex.shiftRight(64).toLong(), idHex.toLong()) } - - val idHex = BigInteger(this, 16) - return UUID(idHex.shiftRight(64).toLong(), idHex.toLong()) + throw IllegalArgumentException("Invalid UUID format: $this") } \ No newline at end of file diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/item/CraftBuilder.kt b/src/main/kotlin/io/github/distractic/bukkit/api/item/CraftBuilder.kt index aa8ba2e7..7c1b38ff 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/item/CraftBuilder.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/item/CraftBuilder.kt @@ -111,10 +111,19 @@ public class CraftBuilder { */ public fun set(positions: Array, item: ItemStack) { positions.forEach { - craft[it.index.toInt()] = item + set(it, item) } } + /** + * Define the item stack at a position on the craft table. + * @param position Position of the item. + * @param item Item. + */ + public fun set(position: CraftSlot, item: ItemStack) { + craft[position.index.toInt()] = item + } + /** * Define the value of the [result] property. * An item is built from the builder and assign it as result of the craft. diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/koin/CraftContext.kt b/src/main/kotlin/io/github/distractic/bukkit/api/koin/CraftContext.kt index 776bf349..ed842dee 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/koin/CraftContext.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/koin/CraftContext.kt @@ -37,7 +37,12 @@ public object CraftContext { /** * [Koin] instanced linked to an app id. */ - private val koins: MutableMap> = mutableMapOf() + private val _koins: MutableMap> = mutableMapOf() + + /** + * [Koin] instanced linked to an app id. + */ + public val koins: Map> = _koins /** * Gets the [Koin] instance for an app. @@ -52,13 +57,13 @@ public object CraftContext { * @param id App id to find the dedicated koin instance. * @return Koin? */ - public fun getOrNull(id: String): Koin? = koins[id]?.second + public fun getOrNull(id: String): Koin? = _koins[id]?.second /** Closes and removes the current [Koin] instance. */ public fun stopKoin(id: String): Unit = synchronized(this) { - val koinInstance = koins[id] ?: return@synchronized + val koinInstance = _koins[id] ?: return@synchronized koinInstance.second?.close() - koins -= id + _koins -= id } /** @@ -68,9 +73,8 @@ public object CraftContext { * * @throws KoinAppAlreadyStartedException The [KoinApplication] has already been instantiated. */ - public fun startKoin(id: String, appDeclaration: KoinAppDeclaration): KoinApplication = synchronized(this) { - val koinApplication = KoinApplication.init() - appDeclaration(koinApplication) + public fun startKoin(id: String, appDeclaration: KoinAppDeclaration = {}): KoinApplication = synchronized(this) { + val koinApplication = KoinApplication.init().apply(appDeclaration) return@synchronized startKoin(id, koinApplication) } @@ -100,7 +104,7 @@ public object CraftContext { throw KoinAppAlreadyStartedException("Koin Application has already been started for id [$id]") } - koins[id] = koinApplication to koinApplication.koin + _koins[id] = koinApplication to koinApplication.koin } /** @@ -109,7 +113,7 @@ public object CraftContext { * @param module The module to load. */ public fun loadKoinModules(id: String, module: Module): Unit = synchronized(this) { - get(id).loadModules(listOf(module)) + loadKoinModules(id, listOf(module)) } /** @@ -127,7 +131,7 @@ public object CraftContext { * @param module The module to unload. */ public fun unloadKoinModules(id: String, module: Module): Unit = synchronized(this) { - get(id).unloadModules(listOf(module)) + unloadKoinModules(id, listOf(module)) } /** diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTask.kt b/src/main/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTask.kt index 54d28b74..544d40cd 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTask.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTask.kt @@ -24,7 +24,7 @@ private val log = KotlinLogging.logger { } */ public class SchedulerTask( coroutineScope: CoroutineScope, - private var delay: Duration, + public var delay: Duration, private var delayBefore: Boolean = false, private var stopWhenNoTask: Boolean = true ) : AbstractScheduler(coroutineScope) { @@ -37,7 +37,7 @@ public class SchedulerTask( */ public inner class Task( public val id: String = UUID.randomUUID().toString(), - private val parent: SchedulerTask, + public val parent: SchedulerTask, public val body: suspend Task.() -> Unit ) { @@ -59,7 +59,7 @@ public class SchedulerTask( private var nextTaskIndex = 0 private val _tasks = ArrayList() - private val tasks: List = _tasks + public val tasks: List = _tasks private val mutex = Mutex() diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/world/Cuboid.kt b/src/main/kotlin/io/github/distractic/bukkit/api/world/Cuboid.kt index 2db21bdf..2bda6a2e 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/world/Cuboid.kt +++ b/src/main/kotlin/io/github/distractic/bukkit/api/world/Cuboid.kt @@ -4,12 +4,14 @@ import io.github.distractic.bukkit.api.extension.minMax import io.github.distractic.bukkit.api.world.exception.WorldDifferentException import org.bukkit.Bukkit import org.bukkit.Location +import org.bukkit.World import org.bukkit.block.Block /** * Represents an area in a minecraft world. - * @property startPosition Position with minimal coordinate's value. - * @property endPosition Position with maximal coordinate's value. + * @property startLocation Position with minimal coordinate's value. + * @property endLocation Position with maximal coordinate's value. + * @property world World where the cuboid is applied. */ public class Cuboid(location1: Location, location2: Location) { @@ -39,8 +41,15 @@ public class Cuboid(location1: Location, location2: Location) { } } - public val startPosition: Location - public val endPosition: Location + public val startLocation: Location + public val endLocation: Location + + public var world: World? + get() = startLocation.world + set(value) { + startLocation.world = value + endLocation.world = value + } init { val world1 = location1.world @@ -52,8 +61,8 @@ public class Cuboid(location1: Location, location2: Location) { val (minX, maxX) = minMax(location1.x, location2.x) val (minY, maxY) = minMax(location1.y, location2.y) val (minZ, maxZ) = minMax(location1.z, location2.z) - startPosition = Location(world1, minX, minY, minZ) - endPosition = Location(world1, maxX, maxY, maxZ) + startLocation = Location(world1, minX, minY, minZ) + endLocation = Location(world1, maxX, maxY, maxZ) } /** @@ -62,22 +71,22 @@ public class Cuboid(location1: Location, location2: Location) { * @param location Location. * @return `true` if the location is between bounds, `false` otherwise. */ - public operator fun contains(location: Location): Boolean = location.world == startPosition.world - && location.x in startPosition.x..endPosition.x - && location.y in startPosition.y..endPosition.y - && location.z in startPosition.z..endPosition.z + public operator fun contains(location: Location): Boolean = location.world == startLocation.world + && location.x in startLocation.x..endLocation.x + && location.y in startLocation.y..endLocation.y + && location.z in startLocation.z..endLocation.z /** * Create an iterator to iterate on all locations present in the area. * @return Iterator of location. */ public fun locationSequence(): Sequence = sequence { - val world = startPosition.world - for (x in startPosition.blockX..endPosition.blockX) { + val world = startLocation.world + for (x in startLocation.blockX..endLocation.blockX) { val xDouble = x.toDouble() - for (y in startPosition.blockY..endPosition.blockY) { + for (y in startLocation.blockY..endLocation.blockY) { val yDouble = y.toDouble() - for (z in startPosition.blockZ..endPosition.blockZ) { + for (z in startLocation.blockZ..endLocation.blockZ) { yield(Location(world, xDouble, yDouble, z.toDouble())) } } diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/AbstractKoinTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/AbstractKoinTest.kt new file mode 100644 index 00000000..d0974bd6 --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/AbstractKoinTest.kt @@ -0,0 +1,51 @@ +package io.github.distractic.bukkit.api + +import io.github.distractic.bukkit.api.koin.CraftContext +import io.github.distractic.bukkit.api.koin.loadModule +import io.github.distractic.bukkit.api.utils.getRandomString +import io.mockk.every +import io.mockk.mockk +import org.bukkit.Server +import org.koin.core.module.Module +import org.koin.dsl.ModuleDeclaration +import kotlin.test.AfterTest +import kotlin.test.BeforeTest + +abstract class AbstractKoinTest { + + lateinit var plugin: Plugin + + lateinit var server: Server + + lateinit var pluginId: String + + @BeforeTest + open fun onBefore() { + pluginId = getRandomString() + CraftContext.startKoin(pluginId) { } + + loadTestModule { + plugin = mockk(getRandomString()) + server = mockk(getRandomString()) + + every { plugin.server } returns server + every { plugin.id } returns pluginId + every { plugin.name } returns getRandomString() + + single { plugin } + single { server } + } + } + + @AfterTest + open fun onAfter() { + CraftContext.stopKoin(pluginId) + } + + inline fun testInject(): T = CraftContext.get(pluginId).inject().value + + fun loadTestModule(moduleDeclaration: ModuleDeclaration): Module = + loadModule(pluginId, false, moduleDeclaration) + + +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/delegate/DelegateWorldTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/delegate/DelegateWorldTest.kt new file mode 100644 index 00000000..8bd84b5d --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/delegate/DelegateWorldTest.kt @@ -0,0 +1,41 @@ +package io.github.distractic.bukkit.api.delegate + +import io.github.distractic.bukkit.api.AbstractKoinTest +import io.mockk.every +import io.mockk.mockk +import org.bukkit.World +import java.util.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class DelegateWorldTest : AbstractKoinTest() { + + @Test + fun `get world if server has it`() { + val uuid = UUID.randomUUID() + val delegate = DelegateWorld(pluginId, uuid) + + val world = mockk() + every { server.getWorld(uuid) } returns world + + val obj = object { + val property: World? by delegate + } + + assertEquals(world, obj.property) + } + + @Test + fun `get world if server doesn't have it`() { + val uuid = UUID.randomUUID() + val delegate = DelegateWorld(pluginId, uuid) + every { server.getWorld(uuid) } returns null + + val obj = object { + val property: World? by delegate + } + + assertNull(obj.property) + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/delegate/PlayerWorldTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/delegate/PlayerWorldTest.kt new file mode 100644 index 00000000..52824427 --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/delegate/PlayerWorldTest.kt @@ -0,0 +1,41 @@ +package io.github.distractic.bukkit.api.delegate + +import io.github.distractic.bukkit.api.AbstractKoinTest +import io.mockk.every +import io.mockk.mockk +import org.bukkit.entity.Player +import org.junit.jupiter.api.Test +import java.util.* +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class PlayerWorldTest : AbstractKoinTest() { + + @Test + fun `get player if server has it`() { + val uuid = UUID.randomUUID() + val delegate = DelegatePlayer(pluginId, uuid) + + val player = mockk() + every { server.getPlayer(uuid) } returns player + + val obj = object { + val property by delegate + } + + assertEquals(player, obj.property) + } + + @Test + fun `get player if server doesn't have it`() { + val uuid = UUID.randomUUID() + val delegate = DelegatePlayer(pluginId, uuid) + every { server.getPlayer(uuid) } returns null + + val obj = object { + val property by delegate + } + + assertNull(obj.property) + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/CommandSenderExtTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/extension/CommandSenderExtTest.kt new file mode 100644 index 00000000..e3922ad8 --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/extension/CommandSenderExtTest.kt @@ -0,0 +1,37 @@ +package io.github.distractic.bukkit.api.extension + +import io.github.distractic.bukkit.api.utils.getRandomString +import io.mockk.justRun +import io.mockk.mockk +import io.mockk.slot +import net.kyori.adventure.text.TextComponent +import net.kyori.adventure.text.format.NamedTextColor +import org.bukkit.command.CommandSender +import org.junit.jupiter.api.Nested +import kotlin.test.Test +import kotlin.test.assertEquals + +class CommandSenderExtTest { + + @Nested + inner class Message { + + @Test + fun `send error message`() { + val sender = mockk() + val slotComponent = slot() + justRun { sender.sendMessage(capture(slotComponent)) } + + val content = getRandomString() + sender.sendMessageError(content) + val component = slotComponent.captured + assertEquals(NamedTextColor.RED, component.color()) + assertEquals(content, component.content()) + } + } + + @Nested + inner class Permissions { + + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/ComparableExtTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/extension/ComparableExtTest.kt new file mode 100644 index 00000000..ce8a05b0 --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/extension/ComparableExtTest.kt @@ -0,0 +1,53 @@ +package io.github.distractic.bukkit.api.extension + +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import kotlin.test.Test +import kotlin.test.assertEquals + +class ComparableExtTest { + + @Nested + @DisplayName("Get min and max") + inner class MinMax { + + @Test + fun `a is inferior to b`() { + val expectedA = 1 + val expectedB = 2 + val (a, b) = minMax(expectedA, expectedB) + assertEquals(expectedA, a) + assertEquals(expectedB, b) + + val (a2, b2) = minMax(expectedB, expectedA) + assertEquals(expectedA, a2) + assertEquals(expectedB, b2) + } + + @Test + fun `a is equals to b`() { + val expectedA = 2 + val expectedB = 2 + val (a, b) = minMax(expectedA, expectedB) + assertEquals(expectedA, a) + assertEquals(expectedB, b) + + val (a2, b2) = minMax(expectedB, expectedA) + assertEquals(expectedA, a2) + assertEquals(expectedB, b2) + } + + @Test + fun `a is superior to b`() { + val expectedA = 3 + val expectedB = 2 + val (b, a) = minMax(expectedA, expectedB) + assertEquals(expectedA, a) + assertEquals(expectedB, b) + + val (b2, a2) = minMax(expectedB, expectedA) + assertEquals(expectedA, a2) + assertEquals(expectedB, b2) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/CoroutineScopeExt.kt b/src/test/kotlin/io/github/distractic/bukkit/api/extension/CoroutineScopeExt.kt new file mode 100644 index 00000000..5ea82471 --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/extension/CoroutineScopeExt.kt @@ -0,0 +1,57 @@ +package io.github.distractic.bukkit.api.extension + +import io.github.distractic.bukkit.api.schedule.SchedulerTask +import kotlinx.coroutines.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds + +class CoroutineScopeExt { + + @Test + fun `with scope context will use the coroutine context`() = runBlocking { + val scope = CoroutineScope(Dispatchers.Default) + val job = withScopeContext(scope) { + val currentJob = this.coroutineContext.job + assertTrue { currentJob in scope.coroutineContext.job.children } + currentJob + } + assertTrue { job !in scope.coroutineContext.job.children } + } + + @Test + fun `create running scheduler with task`() { + val body: suspend SchedulerTask.Task.() -> Unit = {} + val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + val delay = 42.seconds + val scheduler = scope.scheduledTask(delay, body) + assertTrue { scheduler.running } + + assertEquals(delay, scheduler.delay) + assertEquals(1, scheduler.tasks.size) + + val task = scheduler.tasks.first() + assertEquals(body, task.body) + assertEquals(scheduler, task.parent) + + scope.coroutineContext.cancelChildren() + assertFalse { scheduler.running } + } + + @Test + fun `create scheduler`() { + val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + val delay = 10.seconds + val scheduler = scope.scheduler(delay) + assertFalse { scheduler.running } + assertEquals(delay, scheduler.delay) + assertEquals(0, scheduler.tasks.size) + + scheduler.start() + + scope.coroutineContext.cancelChildren() + assertFalse { scheduler.running } + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/DurationExtTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/extension/DurationExtTest.kt new file mode 100644 index 00000000..10904bba --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/extension/DurationExtTest.kt @@ -0,0 +1,114 @@ +package io.github.distractic.bukkit.api.extension + +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds + +class DurationExtTest { + + @Nested + @DisplayName("Int conversion") + inner class IntConversion { + + @Test + fun `positive returns the corresponding ticks`() { + assertEquals(Duration.ZERO, 0.ticks) + assertEquals(50.milliseconds, 1.ticks) + assertEquals(1.seconds, 20.ticks) + assertEquals(2.seconds, 40.ticks) + assertEquals(5.seconds, 100.ticks) + assertEquals(250.milliseconds, 5.ticks) + } + + @Test + fun `negative returns the corresponding ticks`() { + assertEquals((-50).milliseconds, (-1).ticks) + assertEquals((-1).seconds, (-20).ticks) + assertEquals((-2).seconds, (-40).ticks) + assertEquals((-5).seconds, (-100).ticks) + assertEquals((-250).milliseconds, (-5).ticks) + } + + @Test + fun `uInt returns the corresponding ticks`() { + assertEquals(Duration.ZERO, 0u.ticks) + assertEquals(50.milliseconds, 1u.ticks) + assertEquals(1.seconds, 20u.ticks) + assertEquals(2.seconds, 40u.ticks) + assertEquals(5.seconds, 100u.ticks) + assertEquals(250.milliseconds, 5u.ticks) + } + } + + @Nested + @DisplayName("Short conversion") + inner class ShortConversion { + + @Test + fun `positive returns the corresponding ticks`() { + assertEquals(Duration.ZERO, 0.toShort().ticks) + assertEquals(50.milliseconds, 1.toShort().ticks) + assertEquals(1.seconds, 20.toShort().ticks) + assertEquals(2.seconds, 40.toShort().ticks) + assertEquals(5.seconds, 100.toShort().ticks) + assertEquals(250.milliseconds, 5.toShort().ticks) + } + + @Test + fun `negative returns the corresponding ticks`() { + assertEquals((-50).milliseconds, (-1).toShort().ticks) + assertEquals((-1).seconds, (-20).toShort().ticks) + assertEquals((-2).seconds, (-40).toShort().ticks) + assertEquals((-5).seconds, (-100).toShort().ticks) + assertEquals((-250).milliseconds, (-5).toShort().ticks) + } + + @Test + fun `uShort returns the corresponding ticks`() { + assertEquals(Duration.ZERO, 0.toUShort().ticks) + assertEquals(50.milliseconds, 1.toUShort().ticks) + assertEquals(1.seconds, 20.toUShort().ticks) + assertEquals(2.seconds, 40.toUShort().ticks) + assertEquals(5.seconds, 100.toUShort().ticks) + assertEquals(250.milliseconds, 5.toUShort().ticks) + } + } + + @Nested + @DisplayName("Long conversion") + inner class LongConversion { + + @Test + fun `positive returns the corresponding ticks`() { + assertEquals(Duration.ZERO, 0.toLong().ticks) + assertEquals(50.milliseconds, 1.toLong().ticks) + assertEquals(1.seconds, 20.toLong().ticks) + assertEquals(2.seconds, 40.toLong().ticks) + assertEquals(5.seconds, 100.toLong().ticks) + assertEquals(250.milliseconds, 5.toLong().ticks) + } + + @Test + fun `negative returns the corresponding ticks`() { + assertEquals((-50).milliseconds, (-1).toLong().ticks) + assertEquals((-1).seconds, (-20).toLong().ticks) + assertEquals((-2).seconds, (-40).toLong().ticks) + assertEquals((-5).seconds, (-100).toLong().ticks) + assertEquals((-250).milliseconds, (-5).toLong().ticks) + } + + @Test + fun `uLong returns the corresponding ticks`() { + assertEquals(Duration.ZERO, 0.toULong().ticks) + assertEquals(50.milliseconds, 1.toULong().ticks) + assertEquals(1.seconds, 20.toULong().ticks) + assertEquals(2.seconds, 40.toULong().ticks) + assertEquals(5.seconds, 100.toULong().ticks) + assertEquals(250.milliseconds, 5.toULong().ticks) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/EventExtTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/extension/EventExtTest.kt new file mode 100644 index 00000000..e7113573 --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/extension/EventExtTest.kt @@ -0,0 +1,50 @@ +package io.github.distractic.bukkit.api.extension + +import io.mockk.every +import io.mockk.mockk +import org.bukkit.entity.Damageable +import org.bukkit.entity.Entity +import org.bukkit.event.entity.EntityDamageEvent +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import kotlin.random.Random +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class EventExtTest { + + @Nested + inner class EntityDamageEventTest { + + @Nested + @DisplayName("Get final health of damaged") + inner class FinalHealth { + + @Test + fun `null if the damaged is not damaged`() { + val entityNotDamaged = mockk() + val event = mockk() + every { event.entity } returns entityNotDamaged + assertNull(event.finalDamagedHealth()) + } + + @Test + fun `future health computed when entity is damaged`() { + val damaged = mockk() + val currentHealth = Random.nextDouble(Double.MIN_VALUE, Double.MAX_VALUE) + every { damaged.health } returns currentHealth + + val event = mockk() + every { event.entity } returns damaged + + val finalDamage = Random.nextDouble(Double.MIN_VALUE, Double.MAX_VALUE) + every { event.finalDamage } returns finalDamage + assertEquals(currentHealth - finalDamage, event.finalDamagedHealth()) + } + } + + } + + +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/ItemStackExtTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/extension/ItemStackExtTest.kt new file mode 100644 index 00000000..df1b7224 --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/extension/ItemStackExtTest.kt @@ -0,0 +1,326 @@ +package io.github.distractic.bukkit.api.extension + +import io.github.distractic.bukkit.api.utils.getRandomString +import io.mockk.every +import io.mockk.mockk +import org.bukkit.Material +import org.bukkit.inventory.ItemStack +import org.junit.jupiter.api.Nested +import kotlin.test.Test +import kotlin.test.assertEquals + +class ItemStackExtTest { + + @Test + fun `get material property returns the value of type property`() { + val item = item {} + assertEquals(item.type, item.material) + item.type = Material.IRON_SHOVEL + assertEquals(item.type, item.material) + } + + @Test + fun `set material property define the value of type property`() { + val item = item {} + assertEquals(item.type, item.material) + item.material = Material.IRON_SHOVEL + assertEquals(item.type, item.material) + } + + @Test + fun `item function builder create init item stack with air material`() { + val item = item {} + assertEquals(Material.AIR, item.type) + } + + @Test + fun `item function builder set the properties of item stack`() { + val expectedType = Material.STICK + val expectedAmount = 42 + + val item = item { + type = expectedType + amount = expectedAmount + } + + assertEquals(expectedType, item.type) + assertEquals(expectedAmount, item.amount) + } + + @Test + fun `ItemStack function builder create init item stack with air material`() { + val item = ItemStack {} + assertEquals(Material.AIR, item.type) + } + + @Test + fun `ItemStack function builder set the properties of item stack`() { + val expectedType = Material.STICK + val expectedAmount = 42 + + val item = ItemStack { + type = expectedType + amount = expectedAmount + } + + assertEquals(expectedType, item.type) + assertEquals(expectedAmount, item.amount) + } + + @Test + fun `ItemStack function builder set the material by the parameter`() { + val expectedType = Material.STICK + val item = ItemStack(expectedType) { } + assertEquals(expectedType, item.type) + } + + @Test + fun `ItemStack function builder set the properties by lambda`() { + val expectedAmount = 42 + val item = ItemStack(Material.AIR) { + amount = expectedAmount + } + assertEquals(expectedAmount, item.amount) + } + + @Nested + inner class FilterNotAir { + + @Test + fun `array of items`() { + val item1 = mockItem(Material.DIAMOND) + val item2 = mockItem(Material.AMETHYST_BLOCK) + assertEquals( + listOf(item1, item2), arrayOf( + item1, + mockItem(Material.AIR), + item2, + mockItem(Material.AIR) + ).filterNotAir() + ) + } + + @Test + fun `list of items`() { + val item1 = mockItem(Material.DIAMOND_HELMET) + val item2 = mockItem(Material.ATTACHED_PUMPKIN_STEM) + assertEquals( + listOf(item1, item2), listOf( + item1, + mockItem(Material.AIR), + item2, + mockItem(Material.AIR) + ).filterNotAir() + ) + } + + @Test + fun `sequence of items`() { + val item1 = mockItem(Material.BEDROCK) + val item2 = mockItem(Material.ACACIA_LEAVES) + assertEquals( + listOf(item1, item2), listOf( + item1, + mockItem(Material.AIR), + item2, + mockItem(Material.AIR) + ).filterNotAir() + ) + } + } + + @Nested + inner class ItemIndexed { + + @Nested + inner class ArrayNotNullItems { + + @Test + fun `empty array returns empty map`() { + assertEquals(emptyMap(), emptyArray().itemsIndexed()) + } + + @Test + fun `items not air is linked to the index`() { + val item1 = mockItem(Material.ACACIA_DOOR) + val item2 = mockItem(Material.ACTIVATOR_RAIL) + val array = arrayOf( + mockItem(Material.AIR), + item1, + mockItem(Material.AIR), + mockItem(Material.AIR), + item2 + ) + val map = array.itemsIndexed() + val expectedMap = mapOf( + 1 to item1, + 4 to item2 + ) + + assertEquals(expectedMap, map) + } + } + + @Nested + inner class ArrayNullableItems { + + @Test + fun `empty array returns empty map`() { + assertEquals(emptyMap(), emptyArray().itemsIndexed()) + } + + @Test + fun `items not air is linked to the index`() { + val item1 = mockItem(Material.ACACIA_DOOR) + val item2 = mockItem(Material.ACTIVATOR_RAIL) + val array = arrayOfNulls(4) + array[0] = null + array[1] = item1 + array[2] = item2 + array[3] = mockItem(Material.AIR) + + val map = array.itemsIndexed() + val expectedMap = mapOf( + 1 to item1, + 2 to item2 + ) + + assertEquals(expectedMap, map) + } + } + + @Nested + inner class SequenceNotNullItems { + + @Test + fun `empty array returns empty map`() { + assertEquals(emptyMap(), emptySequence().itemsIndexed()) + } + + @Test + fun `items not air is linked to the index`() { + val item1 = mockItem(Material.STICK) + val item2 = mockItem(Material.AMETHYST_BLOCK) + + val sequence = sequenceOf( + item1, + mockItem(Material.AIR), + mockItem(Material.AIR), + item2, + mockItem(Material.AIR) + ) + + val map = sequence.itemsIndexed() + val expectedMap = mapOf( + 0 to item1, + 3 to item2 + ) + + assertEquals(expectedMap, map) + } + } + + @Nested + inner class SequenceNullableItems { + + @Test + fun `empty array returns empty map`() { + assertEquals(emptyMap(), emptySequence().itemsIndexed()) + } + + @Test + fun `items not air is linked to the index`() { + val item1 = mockItem(Material.STICK) + val item2 = mockItem(Material.AMETHYST_BLOCK) + + val sequence = sequenceOf( + item1, + null, + mockItem(Material.AIR), + item2, + null + ) + + val map = sequence.itemsIndexed() + val expectedMap = mapOf( + 0 to item1, + 3 to item2 + ) + + assertEquals(expectedMap, map) + } + } + + @Nested + inner class IterableNotNullItems { + + @Test + fun `empty array returns empty map`() { + assertEquals(emptyMap(), emptyList().itemsIndexed()) + } + + @Test + fun `items not air is linked to the index`() { + val item1 = mockItem(Material.BONE) + val item2 = mockItem(Material.IRON_SWORD) + + val list = listOf( + item1, + mockItem(Material.AIR), + mockItem(Material.AIR), + item1, + item2, + mockItem(Material.AIR) + ) + + val map = list.itemsIndexed() + val expectedMap = mapOf( + 0 to item1, + 3 to item1, + 4 to item2 + ) + + assertEquals(expectedMap, map) + } + } + + @Nested + inner class IterableNullableItems { + + @Test + fun `empty array returns empty map`() { + assertEquals(emptyMap(), emptyList().itemsIndexed()) + } + + @Test + fun `items not air is linked to the index`() { + val item1 = mockItem(Material.BONE) + val item2 = mockItem(Material.IRON_SWORD) + + val list = listOf( + item1, + null, + mockItem(Material.AIR), + item1, + item2, + mockItem(Material.AIR) + ) + + val map = list.itemsIndexed() + val expectedMap = mapOf( + 0 to item1, + 3 to item1, + 4 to item2 + ) + + assertEquals(expectedMap, map) + } + } + } + + private fun mockItem(material: Material): ItemStack { + return mockk(getRandomString()).apply { + every { type } returns material + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/JavaPluginExtTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/extension/JavaPluginExtTest.kt new file mode 100644 index 00000000..a6e85b97 --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/extension/JavaPluginExtTest.kt @@ -0,0 +1,168 @@ +package io.github.distractic.bukkit.api.extension + +import io.github.distractic.bukkit.api.item.CraftSlot +import io.github.distractic.bukkit.api.item.exception.CraftResultMissingException +import io.github.distractic.bukkit.api.utils.getRandomString +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot +import org.bukkit.Material +import org.bukkit.Server +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.Recipe +import org.bukkit.inventory.ShapedRecipe +import org.bukkit.plugin.java.JavaPlugin +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import java.util.* +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals + +class JavaPluginExtTest { + + private lateinit var plugin: JavaPlugin + private lateinit var server: Server + + @BeforeTest + fun onBefore() { + plugin = mockk() + server = mockk() + + every { plugin.server } returns server + every { plugin.name } returns getRandomString() + every { server.pluginManager } returns mockk() + } + + @Nested + @DisplayName("Register craft") + inner class RegisterCraft { + + @Test + fun `no add a final item throws error`() { + assertThrows { + plugin.registerCraft {} + } + } + + @Test + fun `add recipe created into the server`() { + val slotRecipe = slot() + every { server.addRecipe(capture(slotRecipe)) } returns true + + val expectedResult: ItemStack + val expectedKey = "test" + plugin.registerCraft(key = "test") { + result = mockk().also { + every { it.type } returns Material.STICK + every { it.amount } returns 1 + every { it.hasItemMeta() } returns false + } + expectedResult = result!! + } + + val recipe = slotRecipe.captured as ShapedRecipe + assertEquals(expectedKey, recipe.key.key) + + val resultRecipe = recipe.result + assertEquals(expectedResult.amount, resultRecipe.amount) + assertEquals(expectedResult.type, resultRecipe.type) + assertContentEquals(arrayOf(" ", " ", " "), recipe.shape) + } + + @Test + fun `add the defined recipe from the builder`() { + val slotRecipe = slot() + every { server.addRecipe(capture(slotRecipe)) } returns true + + plugin.registerCraft { + val item1 = mockk() + val item2 = mockk() + + set(CraftSlot.TopLeft, item = item1) + set(CraftSlot.Top, item = item2) + set(CraftSlot.TopRight, item = mockk()) + + set(CraftSlot.CenterLeft, item = mockk()) + set(CraftSlot.Center, item = item1) + set(CraftSlot.CenterRight, item = mockk()) + + set(CraftSlot.BottomLeft, item = item2) + set(CraftSlot.Bottom, item = mockk()) + set(CraftSlot.BottomRight, item = mockk()) + result = mockk().also { + every { it.type } returns Material.ACACIA_BUTTON + every { it.amount } returns 1 + every { it.hasItemMeta() } returns false + } + } + + val recipe = slotRecipe.captured as ShapedRecipe + assertContentEquals(arrayOf("ABC", "DAE", "BFG"), recipe.shape) + } + + @Test + fun `add all different item will create different designation`() { + val slotRecipe = slot() + every { server.addRecipe(capture(slotRecipe)) } returns true + + plugin.registerCraft { + for (index in CraftSlot.values()) { + set(index, item = mockk()) + } + + result = mockk().also { + every { it.type } returns Material.ACACIA_BUTTON + every { it.amount } returns 1 + every { it.hasItemMeta() } returns false + } + } + + val recipe = slotRecipe.captured as ShapedRecipe + assertContentEquals(arrayOf("ABC", "DEF", "GHI"), recipe.shape) + } + + @Test + fun `add several times the same item will create once designation`() { + val slotRecipe = slot() + every { server.addRecipe(capture(slotRecipe)) } returns true + + plugin.registerCraft { + val item = mockk() + for (index in CraftSlot.values()) { + set(index, item = item) + } + result = mockk().also { + every { it.type } returns Material.ACACIA_BUTTON + every { it.amount } returns 1 + every { it.hasItemMeta() } returns false + } + } + + val recipe = slotRecipe.captured as ShapedRecipe + assertContentEquals(arrayOf("AAA", "AAA", "AAA"), recipe.shape) + } + + @Test + fun `no key add a default UUID name`() { + val slotRecipe = slot() + every { server.addRecipe(capture(slotRecipe)) } returns true + + plugin.registerCraft { + result = mockk().also { + every { it.type } returns Material.STICK + every { it.amount } returns 1 + every { it.hasItemMeta() } returns false + } + } + + val recipe = slotRecipe.captured as ShapedRecipe + val key = recipe.key.key + UUID.fromString(key) + } + + } + +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/LocationExtTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/extension/LocationExtTest.kt new file mode 100644 index 00000000..53eb6e6e --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/extension/LocationExtTest.kt @@ -0,0 +1,142 @@ +package io.github.distractic.bukkit.api.extension + +import io.github.distractic.bukkit.api.extension.* +import io.github.distractic.bukkit.api.utils.getRandomString +import io.mockk.mockk +import org.bukkit.Location +import org.bukkit.World +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class LocationExtTest { + + private lateinit var loc: Location + + @BeforeTest + fun onBefore() { + loc = Location(mockk(getRandomString()), 0.0, 1.0, 2.0, 3.0f, 4.0f) + } + + @Test + fun `center the location`() { + val expectedX = loc.blockX + 0.5 + val expectedZ = loc.blockZ + 0.5 + val locCenter = loc.center() + assertEquals(expectedX, locCenter.x) + assertEquals(expectedZ, locCenter.z) + } + + @Nested + @DisplayName("Components") + inner class Components { + + @Test + fun `component1 give the world property`() { + assertEquals(loc.world, loc.component1()) + } + + @Test + fun `component2 give the x property`() { + assertEquals(loc.x, loc.component2()) + } + + @Test + fun `component3 give the y property`() { + assertEquals(loc.y, loc.component3()) + } + + @Test + fun `component4 give the z property`() { + assertEquals(loc.z, loc.component4()) + } + + @Test + fun `component5 give the yaw property`() { + assertEquals(loc.yaw, loc.component5()) + } + + @Test + fun `component6 give the pitch property`() { + assertEquals(loc.pitch, loc.component6()) + } + } + + @Nested + @DisplayName("Copy properties") + inner class Copy { + + @Test + fun `copyFrom will copy properties into the instance`() { + val dest = Location(mockk("World 2"), -1.0, -1.0, -1.0, -1.0f, -1.0f) + + dest.copyFrom(loc) + assertEquals(loc.world, dest.world) + assertEquals(loc.x, dest.x) + assertEquals(loc.y, dest.y) + assertEquals(loc.z, dest.z) + assertEquals(loc.yaw, dest.yaw) + assertEquals(loc.pitch, dest.pitch) + } + + @Test + fun `copy without arg will clone the location`() { + assertEquals(loc.clone(), loc.copy()) + } + + @Test + fun `copy create a new instance`() { + assertTrue { loc !== loc.copy() } + } + + @Test + fun `copy with only world property will change only the property`() { + val world = mockk(getRandomString()) + assertEquals(Location(world, loc.x, loc.y, loc.z, loc.yaw, loc.pitch), loc.copy(world = world)) + } + + @Test + fun `copy with only x property will change only the property`() { + val x = loc.x + 10 + assertEquals(Location(loc.world, x, loc.y, loc.z, loc.yaw, loc.pitch), loc.copy(x = x)) + } + + @Test + fun `copy with only y property will change only the property`() { + val y = loc.y + 10 + assertEquals(Location(loc.world, loc.x, y, loc.z, loc.yaw, loc.pitch), loc.copy(y = y)) + } + + @Test + fun `copy with only z property will change only the property`() { + val z = loc.z + 10 + assertEquals(Location(loc.world, loc.x, loc.y, z, loc.yaw, loc.pitch), loc.copy(z = z)) + } + + @Test + fun `copy with only yaw property will change only the property`() { + val yaw = loc.yaw + 10 + assertEquals(Location(loc.world, loc.x, loc.y, loc.z, yaw, loc.pitch), loc.copy(yaw = yaw)) + } + + @Test + fun `copy with only pitch property will change only the property`() { + val pitch = loc.pitch + 10 + assertEquals(Location(loc.world, loc.x, loc.y, loc.z, loc.yaw, pitch), loc.copy(pitch = pitch)) + } + + @Test + fun `copy with all args will change all properties`() { + val world = mockk(getRandomString()) + val x = loc.x + 10 + val y = loc.y + 20 + val z = loc.z + 30 + val yaw = loc.yaw + 40 + val pitch = loc.pitch + 50 + assertEquals(Location(world, x, y, z, yaw, pitch), loc.copy(world, x, y, z, yaw, pitch)) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/MerchantRecipeExtTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/extension/MerchantRecipeExtTest.kt new file mode 100644 index 00000000..0d8c3bc8 --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/extension/MerchantRecipeExtTest.kt @@ -0,0 +1,86 @@ +package io.github.distractic.bukkit.api.extension + +import io.github.distractic.bukkit.api.utils.getRandomString +import io.mockk.mockk +import org.bukkit.Material +import org.bukkit.inventory.ItemStack +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class MerchantRecipeExtTest { + + @Test + fun `constructor utils function`() { + val result = mockk(getRandomString()) + val maxUses = 1 + val uses = 2 + val experienceReward = false + val villagerExperience = 3 + val priceMultiplier = 1.1F + val demand = 4 + val specialPrice = 5 + val ignoreDiscounts = true + + val item1 = ItemStack(Material.COOKED_BEEF) + val item2 = ItemStack(Material.ACACIA_DOOR) + val ingredients = listOf(item1, item2) + + var recipe = MerchantRecipe( + result = result, + maxUses = maxUses, + uses = uses, + experienceReward = experienceReward, + villagerExperience = villagerExperience, + priceMultiplier = priceMultiplier, + demand = demand, + specialPrice = specialPrice, + ignoreDiscounts = ignoreDiscounts, + ingredients = ingredients + ) + + assertEquals(result, recipe.result) + assertEquals(maxUses, recipe.maxUses) + assertEquals(uses, recipe.uses) + assertEquals(experienceReward, recipe.hasExperienceReward()) + assertEquals(villagerExperience, recipe.villagerExperience) + assertEquals(priceMultiplier, recipe.priceMultiplier) + assertEquals(demand, recipe.demand) + assertEquals(specialPrice, recipe.specialPrice) + assertEquals(ignoreDiscounts, recipe.shouldIgnoreDiscounts()) + // Problem with Bukkit server + // assertEquals(ingredients, recipe.ingredients) + + recipe = MerchantRecipe( + result = result, + maxUses = 0, + experienceReward = true, + ignoreDiscounts = false, + ) + + assertTrue { recipe.hasExperienceReward() } + assertFalse { recipe.shouldIgnoreDiscounts() } + } + + @Test + fun `default constructor utils function`() { + val result = mockk(getRandomString()) + val maxUses = 10 + val recipe = MerchantRecipe( + result = result, + maxUses = maxUses + ) + + assertEquals(result, recipe.result) + assertEquals(maxUses, recipe.maxUses) + assertEquals(0, recipe.uses) + assertEquals(false, recipe.hasExperienceReward()) + assertEquals(0, recipe.villagerExperience) + assertEquals(0.0f, recipe.priceMultiplier) + assertEquals(0, recipe.demand) + assertEquals(0, recipe.specialPrice) + assertEquals(false, recipe.shouldIgnoreDiscounts()) + assertEquals(emptyList(), recipe.ingredients) + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/PersistentDataHolderTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/extension/PersistentDataHolderTest.kt new file mode 100644 index 00000000..643c693d --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/extension/PersistentDataHolderTest.kt @@ -0,0 +1,23 @@ +package io.github.distractic.bukkit.api.extension + +import io.github.distractic.bukkit.api.utils.getRandomString +import io.mockk.mockk +import org.bukkit.persistence.PersistentDataContainer +import org.bukkit.persistence.PersistentDataHolder +import kotlin.test.Test +import kotlin.test.assertTrue + +class PersistentDataHolderTest { + + @Test + fun `open data container and manage it`() { + val container = mockk(getRandomString()) + val holder = PersistentDataHolder { container } + + val isEquals = holder.dataContainer { + this == container + } + assertTrue { isEquals } + } + +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/PlayerExtTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/extension/PlayerExtTest.kt new file mode 100644 index 00000000..3ab28664 --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/extension/PlayerExtTest.kt @@ -0,0 +1,103 @@ +package io.github.distractic.bukkit.api.extension + +import com.destroystokyo.paper.profile.PlayerProfile +import io.github.distractic.bukkit.api.utils.getRandomString +import io.mockk.every +import io.mockk.justRun +import io.mockk.mockk +import io.mockk.slot +import org.bukkit.Material +import org.bukkit.entity.Player +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.PlayerInventory +import org.junit.jupiter.api.Nested +import kotlin.test.* + +class PlayerExtTest { + + @Nested + inner class Profile { + + @Test + fun `edit profile use the current profile and redefine it with the same modified instance`() { + val player = mockk(getRandomString()) + val profile = mockk(getRandomString()) + every { player.playerProfile } returns profile + + val slotName = slot() + every { profile.setName(capture(slotName)) } returns "" + + val slotProfile = slot() + justRun { player.playerProfile = capture(slotProfile) } + + val expectedName = getRandomString() + player.editProfile { + setName(expectedName) + } + + assertEquals(profile, slotProfile.captured) + assertEquals(expectedName, slotName.captured) + } + } + + @Nested + inner class ItemInHand { + + private lateinit var player: Player + private val inventory get() = player.inventory + + @BeforeTest + fun onBefore() { + player = mockk(getRandomString()) + val inventory = mockk() + every { player.inventory } returns inventory + } + + @Test + fun `compare with equals and item found`() { + val expectedItem = mockk(getRandomString()) + every { inventory.itemInMainHand } returns expectedItem + every { inventory.itemInOffHand } returns mockk(getRandomString()) + + assertTrue { player.itemInHand(expectedItem) } + + every { inventory.itemInMainHand } returns mockk(getRandomString()) + every { inventory.itemInOffHand } returns expectedItem + + assertTrue { player.itemInHand(expectedItem) } + } + + @Test + fun `compare with equals and item not found`() { + val expectedItem = mockk(getRandomString()) + every { inventory.itemInMainHand } returns mockk(getRandomString()) + every { inventory.itemInOffHand } returns mockk(getRandomString()) + + assertFalse { player.itemInHand(expectedItem) } + } + + @Test + fun `compare with lambda and item found`() { + val expectedItem = ItemStack(Material.ACACIA_DOOR) + every { inventory.itemInMainHand } returns expectedItem + every { inventory.itemInOffHand } returns ItemStack(Material.AIR) + + assertTrue { player.itemInHand { it.type == expectedItem.type } } + + every { inventory.itemInMainHand } returns ItemStack(Material.COOKED_BEEF) + every { inventory.itemInOffHand } returns expectedItem + + assertTrue { player.itemInHand { it.type == expectedItem.type } } + } + + @Test + fun `compare with lambda and item not found`() { + val expectedItem = ItemStack(Material.ACACIA_DOOR) + every { inventory.itemInMainHand } returns ItemStack(Material.SWEET_BERRIES) + every { inventory.itemInOffHand } returns ItemStack(Material.BLUE_WOOL) + + assertFalse { player.itemInHand { expectedItem.isSimilar(it) } } + } + } + +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/RunnableExtTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/extension/RunnableExtTest.kt new file mode 100644 index 00000000..b2eeb396 --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/extension/RunnableExtTest.kt @@ -0,0 +1,17 @@ +package io.github.distractic.bukkit.api.extension + +import kotlin.test.Test +import kotlin.test.assertTrue + +class RunnableExtTest { + + @Test + fun `create bukkit runnable instance`() { + var isCalled = false + val runnable = BukkitRunnable { + isCalled = true + } + runnable.run() + assertTrue { isCalled } + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/StringExtTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/extension/StringExtTest.kt new file mode 100644 index 00000000..00043e91 --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/extension/StringExtTest.kt @@ -0,0 +1,128 @@ +package io.github.distractic.bukkit.api.extension + +import io.github.distractic.bukkit.api.extension.* +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import java.util.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class StringExtTest { + + @Nested + @DisplayName("Colored sentence") + inner class Colored { + + @Test + fun `no change if there is no specific char`() { + fun verifyIntegrity(sentence: String) { + assertEquals(sentence, sentence.colored()) + } + verifyIntegrity("Hello minecraft world !") + verifyIntegrity("§4It's a good §6day.") + } + + @Test + fun `change applied if there is specific char`() { + assertEquals("§1Hello §2minecraft §3world !", "&1Hello &2minecraft &3world !".colored()) + assertEquals("§4It's a good day.", "&4It's a good day.".colored()) + } + } + + @Nested + @DisplayName("Base64") + inner class Base64Test { + + @Test + fun `encode string`() { + assertEquals("SGVsbG8gd29ybGQ=", "Hello world".encodeBase64ToString()) + assertEquals("w6LDr8O5LSo=", "âïù-*".encodeBase64ToString()) + assertEquals("2LXYqNin2K0g2KfZhNiu2YrYsQ==", "صباح الخير".encodeBase64ToString()) + assertEquals("44GT44KT44Gr44Gh44Gv", "こんにちは".encodeBase64ToString()) + } + + @Test + fun `decode string`() { + assertEquals("Hello world", "SGVsbG8gd29ybGQ=".decodeBase64ToString()) + assertEquals("âïù-*", "w6LDr8O5LSo=".decodeBase64ToString()) + assertEquals("صباح الخير", "2LXYqNin2K0g2KfZhNiu2YrYsQ==".decodeBase64ToString()) + assertEquals("こんにちは", "44GT44KT44Gr44Gh44Gv".decodeBase64ToString()) + } + } + + @Nested + @DisplayName("Conversion UUID") + inner class ConversionUUID { + + @Nested + inner class Strict { + + @Test + fun `can convert if the string is valid`() { + val uuid = UUID.randomUUID() + val string = uuid.toString() + assertEquals(uuid, string.toUUIDStrict()) + } + + @ParameterizedTest + @ValueSource(strings = ["", "a", "c7e4ca3236d942408e53de44bef8eeeb", "c7e4ca32-36d9-4240-8e53-de44bef8eeeba"]) + fun `throws exception if invalid`(value: String) { + assertThrows { + value.toUUIDStrict() + } + } + + @Test + fun `non null value when value can be converted`() { + val uuid = UUID.randomUUID() + val string = uuid.toString() + assertEquals(uuid, string.toUUIDStrictOrNull()) + } + + @ParameterizedTest + @ValueSource(strings = ["", "a", "c7e4ca3236d942408e53de44bef8eeeb", "c7e4ca32-36d9-4240-8e53-de44bef8eeeba"]) + fun `nulls if invalid`(value: String) { + assertNull(value.toUUIDStrictOrNull()) + } + + } + + @Nested + inner class NoStrict { + + @ParameterizedTest + @ValueSource(strings = ["c7e4ca3236d942408e53de44bef8eeeb", "c7e4ca32-36d9-4240-8e53-de44bef8eeeb"]) + fun `can convert if the string is valid`(value: String) { + val uuid = UUID.fromString("c7e4ca32-36d9-4240-8e53-de44bef8eeeb") + assertEquals(uuid, value.toUUID()) + } + + @ParameterizedTest + @ValueSource(strings = ["", "a", "c7e4ca3236d942408e53de44bef8eeeba", "c7e4ca32-36d9-4240-8e53-de44bef8eeeba"]) + fun `throws exception if invalid`(value: String) { + assertThrows { + value.toUUID() + } + } + + @ParameterizedTest + @ValueSource(strings = ["c7e4ca3236d942408e53de44bef8eeeb", "c7e4ca32-36d9-4240-8e53-de44bef8eeeb"]) + fun `non null value when value can be converted`(value: String) { + val uuid = UUID.fromString("c7e4ca32-36d9-4240-8e53-de44bef8eeeb") + assertEquals(uuid, value.toUUIDOrNull()) + } + + @ParameterizedTest + @ValueSource(strings = ["", "a", "c7e4ca3236d942408e53de44bef8eeeba", "c7e4ca32-36d9-4240-8e53-de44bef8eeeba"]) + fun `nulls if invalid`(value: String) { + assertNull(value.toUUIDStrictOrNull()) + } + + } + + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/VillagerExtTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/extension/VillagerExtTest.kt new file mode 100644 index 00000000..0185517a --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/extension/VillagerExtTest.kt @@ -0,0 +1,88 @@ +package io.github.distractic.bukkit.api.extension + +import io.github.distractic.bukkit.api.Plugin +import io.github.distractic.bukkit.api.utils.getRandomString +import io.mockk.every +import io.mockk.justRun +import io.mockk.mockk +import io.mockk.slot +import org.bukkit.NamespacedKey +import org.bukkit.entity.Villager +import org.bukkit.persistence.PersistentDataContainer +import org.bukkit.persistence.PersistentDataType +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import kotlin.test.* + +class VillagerExtTest { + + private lateinit var plugin: Plugin + + @BeforeTest + fun onBefore() { + plugin = mockk() + every { plugin.name } returns "test" + } + + @Nested + @DisplayName("Keep profession") + inner class KeepProfession { + + @Test + fun `returns true when data is present`() { + val villager = mockk(getRandomString()) + val container = mockk() + + val slotNamespaced = slot() + every { container.get(capture(slotNamespaced), PersistentDataType.BYTE) } returns 0.toByte() + every { villager.persistentDataContainer } returns container + assertTrue { villager.keepProfession(plugin) } + assertEquals(namespacedKeyKeepJob(plugin), slotNamespaced.captured) + } + + @Test + fun `returns false when data is not present`() { + val villager = mockk(getRandomString()) + val container = mockk() + + val slotNamespaced = slot() + every { container.get(capture(slotNamespaced), PersistentDataType.BYTE) } returns null + every { villager.persistentDataContainer } returns container + assertFalse { villager.keepProfession(plugin) } + assertEquals(namespacedKeyKeepJob(plugin), slotNamespaced.captured) + } + + } + + @Nested + @DisplayName("Set keep profession") + inner class SetKeepProfession { + + @Test + fun `when true, set key into the data container`() { + val villager = mockk(getRandomString()) + val container = mockk() + + val slotNamespaced = slot() + justRun { container.set(capture(slotNamespaced), PersistentDataType.BYTE, 0) } + every { villager.persistentDataContainer } returns container + + villager.keepProfession(plugin, true) + assertEquals(namespacedKeyKeepJob(plugin), slotNamespaced.captured) + } + + @Test + fun `when false, remove key into the data container`() { + val villager = mockk(getRandomString()) + val container = mockk() + + val slotNamespaced = slot() + justRun { container.remove(capture(slotNamespaced)) } + every { villager.persistentDataContainer } returns container + + villager.keepProfession(plugin, false) + assertEquals(namespacedKeyKeepJob(plugin), slotNamespaced.captured) + } + + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/WorldExtTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/extension/WorldExtTest.kt new file mode 100644 index 00000000..95cdfe75 --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/extension/WorldExtTest.kt @@ -0,0 +1,95 @@ +package io.github.distractic.bukkit.api.extension + +import io.github.distractic.bukkit.api.utils.createRandomLocation +import io.github.distractic.bukkit.api.utils.getRandomString +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot +import kotlinx.coroutines.runBlocking +import org.bukkit.Chunk +import org.bukkit.Location +import org.bukkit.World +import org.bukkit.block.Block +import java.util.concurrent.CompletableFuture +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class WorldExtTest { + + @Test + fun `await chunk at with block`() = runBlocking { + val world = mockk(getRandomString()) + val slotBlock = slot() + val slotGen = slot() + + val chunk = mockk(getRandomString()) + every { world.getChunkAtAsync(capture(slotBlock), capture(slotGen)) } returns CompletableFuture.completedFuture( + chunk + ) + + val block = mockk(getRandomString()) + assertEquals(chunk, world.awaitChunkAt(block, true)) + assertEquals(block, slotBlock.captured) + assertTrue { slotGen.captured } + assertEquals(chunk, world.awaitChunkAt(block, false)) + assertFalse { slotGen.captured } + } + + @Test + fun `await chunk at with location`() = runBlocking { + val world = mockk(getRandomString()) + val slotLoc = slot() + val slotGen = slot() + + val chunk = mockk(getRandomString()) + every { world.getChunkAtAsync(capture(slotLoc), capture(slotGen)) } returns CompletableFuture.completedFuture( + chunk + ) + + val location = createRandomLocation() + assertEquals(chunk, world.awaitChunkAt(location, true)) + assertEquals(location, slotLoc.captured) + assertTrue { slotGen.captured } + assertEquals(chunk, world.awaitChunkAt(location, false)) + assertFalse { slotGen.captured } + } + + @Test + fun `await chunk at with coord`() = runBlocking { + val world = mockk(getRandomString()) + val slotX = slot() + val slotZ = slot() + val slotGen = slot() + val slotUrgent = slot() + + val chunk = mockk(getRandomString()) + every { + world.getChunkAtAsync( + capture(slotX), + capture(slotZ), + capture(slotGen), + capture(slotUrgent) + ) + } returns CompletableFuture.completedFuture( + chunk + ) + + var x = 10 + var z = 20 + assertEquals(chunk, world.awaitChunkAt(x, z, gen = true, urgent = true)) + assertEquals(x, slotX.captured) + assertEquals(z, slotZ.captured) + assertTrue { slotGen.captured } + assertTrue { slotUrgent.captured } + + x = 42 + z = 64 + assertEquals(chunk, world.awaitChunkAt(x, z, gen = false, urgent = false)) + assertEquals(x, slotX.captured) + assertEquals(z, slotZ.captured) + assertFalse { slotGen.captured } + assertFalse { slotUrgent.captured } + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/koin/CraftContextTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/koin/CraftContextTest.kt new file mode 100644 index 00000000..f77da6c2 --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/koin/CraftContextTest.kt @@ -0,0 +1,341 @@ +package io.github.distractic.bukkit.api.koin + +import io.github.distractic.bukkit.api.utils.getRandomString +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.koin.core.KoinApplication +import org.koin.core.error.ClosedScopeException +import org.koin.core.error.KoinAppAlreadyStartedException +import org.koin.core.error.NoBeanDefFoundException +import org.koin.dsl.module +import kotlin.test.* + +class CraftContextTest { + + @BeforeTest + fun onBefore() { + CraftContext.koins.toMap().forEach { (id, _) -> + CraftContext.stopKoin(id) + } + } + + @Nested + @DisplayName("Get koin context") + inner class Get { + + @Test + fun `when no instance exists for the id`() { + assertThrows { + CraftContext.get(getRandomString()) + } + } + + @Test + fun `when instance is registered for the id`() { + val id = getRandomString() + CraftContext.startKoin(id) + val koin = CraftContext.get(id) + assertNotNull(koin) + } + } + + @Nested + @DisplayName("Get koin context or null") + inner class GetOrNull { + + @Test + fun `when no instance exists for the id`() { + assertNull(CraftContext.getOrNull(getRandomString())) + } + + @Test + fun `when instance is registered for the id`() { + val id = getRandomString() + CraftContext.startKoin(id) + val koin = CraftContext.get(id) + assertNotNull(koin) + } + } + + @Nested + @DisplayName("Stop koin context") + inner class StopKoin { + + @Test + fun `when no instance exists for the id`() { + CraftContext.stopKoin(getRandomString()) + } + + @Test + fun `when instance is registered for the id`() { + assertEquals(0, CraftContext.koins.size) + val id = getRandomString() + CraftContext.startKoin(id) + val module = module { + single { 1.2 } + } + assertEquals(1, CraftContext.koins.size) + + val koin = CraftContext.get(id) + koin.loadModules(listOf(module)) + assertEquals(1.2, koin.get()) + + CraftContext.stopKoin(id) + assertEquals(0, CraftContext.koins.size) + assertNull(CraftContext.getOrNull(id)) + assertThrows { + assertEquals(1.2, koin.get()) + } + } + } + + @Nested + @DisplayName("Start koin context") + inner class StartKoin { + + @Test + fun `when no instance exists for the id`() { + assertEquals(0, CraftContext.koins.size) + CraftContext.startKoin(getRandomString()) {} + assertEquals(1, CraftContext.koins.size) + } + + @Test + fun `when instance already exists for the id`() { + assertEquals(0, CraftContext.koins.size) + val id = getRandomString() + CraftContext.startKoin(id) {} + assertEquals(1, CraftContext.koins.size) + assertThrows { + CraftContext.startKoin(id) {} + } + assertEquals(1, CraftContext.koins.size) + } + + @Test + fun `define module in declaration during starting`() { + assertEquals(0, CraftContext.koins.size) + val id = getRandomString() + CraftContext.startKoin(id) { + this.modules(module { + single { "hello" } + }) + } + val injectedString = CraftContext.get(id).get() + assertEquals("hello", injectedString) + assertEquals(1, CraftContext.koins.size) + } + } + + @Nested + @DisplayName("Start with koin application") + inner class StartKoinApplication { + + @Test + fun `when no instance exists for the id`() { + assertEquals(0, CraftContext.koins.size) + val koinApplication = KoinApplication.init() + CraftContext.startKoin(getRandomString(), koinApplication) + assertEquals(1, CraftContext.koins.size) + } + + @Test + fun `when instance already exists for the id`() { + assertEquals(0, CraftContext.koins.size) + val id = getRandomString() + val koinApplication = KoinApplication.init() + CraftContext.startKoin(id, koinApplication) + assertEquals(1, CraftContext.koins.size) + assertThrows { + CraftContext.startKoin(id, koinApplication) + } + assertEquals(1, CraftContext.koins.size) + } + + @Test + fun `define module in declaration during starting`() { + assertEquals(0, CraftContext.koins.size) + val koinApplication = KoinApplication.init() + koinApplication.modules(module { + single { "hello" } + }) + val id = getRandomString() + CraftContext.startKoin(id, koinApplication) + val injectedString = CraftContext.get(id).get() + assertEquals("hello", injectedString) + assertEquals(1, CraftContext.koins.size) + } + } + + @Nested + @DisplayName("Load koin module with single module") + inner class LoadKoinSingleModule { + + @Test + fun `when no instance exists for the id`() { + assertThrows { + CraftContext.loadKoinModules(getRandomString(), module { }) + } + } + + @Test + fun `when instance exists for the id`() { + val id = getRandomString() + CraftContext.startKoin(id) + CraftContext.loadKoinModules(id, module { + single { "hello" } + }) + assertEquals("hello", CraftContext.get(id).get()) + } + } + + @Nested + @DisplayName("Load koin module with several modules") + inner class LoadKoinListModule { + + @Test + fun `when no instance exists for the id`() { + assertThrows { + CraftContext.loadKoinModules(getRandomString(), emptyList()) + } + } + + @Test + fun `when instance exists for the id`() { + val id = getRandomString() + CraftContext.startKoin(id) + CraftContext.loadKoinModules(id, listOf( + module { + single { "hello" } + }, module { + single { 42 } + }) + ) + assertEquals("hello", CraftContext.get(id).get()) + assertEquals(42, CraftContext.get(id).get()) + } + } + + @Nested + @DisplayName("Unload koin module with single module") + inner class UnloadKoinSingleModule { + + @Test + fun `when no instance exists for the id`() { + assertThrows { + CraftContext.unloadKoinModules(getRandomString(), module { }) + } + } + + @Test + fun `when instance exists for the id but not module linked`() { + val id = getRandomString() + CraftContext.startKoin(id) + CraftContext.unloadKoinModules(id, module { + single { "hello" } + }) + } + + @Test + fun `when instance exists for the id`() { + val id = getRandomString() + CraftContext.startKoin(id) + val module = module { + single { "hello" } + } + CraftContext.loadKoinModules(id, module) + assertEquals("hello", CraftContext.get(id).get()) + + CraftContext.unloadKoinModules(id, module) + assertThrows { + assertEquals("hello", CraftContext.get(id).get()) + } + } + } + + @Nested + @DisplayName("Unload koin module with several modules") + inner class UnloadKoinListModule { + + @Test + fun `when no instance exists for the id`() { + assertThrows { + CraftContext.unloadKoinModules(getRandomString(), emptyList()) + } + } + + @Test + fun `when instance exists for the id but not module linked`() { + val id = getRandomString() + CraftContext.startKoin(id) + CraftContext.unloadKoinModules(id, listOf(module { + single { "hello" } + })) + } + + @Test + fun `when instance exists for the id`() { + val id = getRandomString() + CraftContext.startKoin(id) + val module1 = module { + single { "hello" } + } + val module2 = module { + single { 1 } + } + val module3 = module { + single { 1.2 } + } + CraftContext.loadKoinModules(id, listOf(module1, module2, module3)) + assertEquals("hello", CraftContext.get(id).get()) + assertEquals(1, CraftContext.get(id).get()) + assertEquals(1.2, CraftContext.get(id).get()) + + CraftContext.unloadKoinModules(id, listOf(module1)) + assertThrows { + assertEquals("hello", CraftContext.get(id).get()) + } + assertEquals(1, CraftContext.get(id).get()) + assertEquals(1.2, CraftContext.get(id).get()) + + CraftContext.unloadKoinModules(id, listOf(module2, module3)) + assertThrows { + assertEquals(1, CraftContext.get(id).get()) + } + + assertThrows { + assertEquals(1.2, CraftContext.get(id).get()) + } + } + } + + @Nested + @DisplayName("Load koin module with lazy extension function") + inner class LoadKoinSingleModuleExtension { + + @Test + fun `when no instance exists for the id`() { + assertThrows { + loadModule(getRandomString()) {} + } + } + + @Test + fun `when instance exists for the id with lazy module creation`() { + val id = getRandomString() + CraftContext.startKoin(id) + var isInit = false + loadModule(id) { + single { + isInit = true + "hello" + } + } + assertFalse { isInit } + assertEquals("hello", CraftContext.get(id).get()) + assertTrue { isInit } + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/listener/PlayerListenerTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/listener/PlayerListenerTest.kt new file mode 100644 index 00000000..438970c9 --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/listener/PlayerListenerTest.kt @@ -0,0 +1,131 @@ +package io.github.distractic.bukkit.api.listener + +import io.github.distractic.bukkit.api.AbstractKoinTest +import io.github.distractic.bukkit.api.player.Client +import io.github.distractic.bukkit.api.player.ClientManager +import io.github.distractic.bukkit.api.player.ClientManagerImpl +import io.github.distractic.bukkit.api.player.exception.ClientAlreadyExistsException +import io.github.distractic.bukkit.api.utils.getRandomString +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.isActive +import kotlinx.coroutines.test.runTest +import net.kyori.adventure.text.Component +import org.bukkit.entity.Player +import org.bukkit.event.player.PlayerJoinEvent +import org.bukkit.event.player.PlayerQuitEvent +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import java.util.* +import kotlin.test.* + +class PlayerListenerTest : AbstractKoinTest() { + + lateinit var clientManager: ClientManager + + private lateinit var listener: PlayerListener + + @BeforeTest + override fun onBefore() { + super.onBefore() + clientManager = ClientManagerImpl() + loadTestModule { + single { clientManager } + } + listener = PlayerListener(plugin) + } + + @Test + fun `contains no clients when instance is created`() { + assertTrue { clientManager.clients.isEmpty() } + } + + @Nested + @DisplayName("Player join") + inner class PlayerJoin { + + private lateinit var event: PlayerJoinEvent + private lateinit var player: Player + + @BeforeTest + fun onBefore() { + player = createPlayerMock() + event = createEvent(player) + } + + @Test + fun `create and save a new client`() = runTest { + val client = createClient(player) + every { plugin.createClient(any()) } returns client + + listener.onJoin(event) + + val clients = clientManager.clients + assertEquals(1, clients.size) + assertTrue { clientManager.contains(player) } + assertEquals(client, clients.values.first()) + + val otherPlayer = createPlayerMock() + val otherEvent = createEvent(otherPlayer) + + val otherClient = createClient(otherPlayer) + every { plugin.createClient(otherPlayer) } returns otherClient + + listener.onJoin(otherEvent) + assertEquals(2, clients.size) + assertTrue { clientManager.contains(player) && clientManager.contains(otherPlayer) } + clients.values.containsAll(listOf(client, otherClient)) + } + + @Test + fun `try to store a client with the same name but already exists and keep first instance`() = runTest { + val client = createClient(player) + every { plugin.createClient(player) } returns client + + listener.onJoin(event) + assertThrows { + listener.onJoin(event) + } + } + + private fun createEvent(player: Player): PlayerJoinEvent { + return PlayerJoinEvent(player, mockk()) + } + } + + @Nested + @DisplayName("Player leave") + inner class PlayerLeave { + + @Test + fun `client linked to the player is removed and cancelled`() = runTest { + val player = createPlayerMock() + val client = Client(pluginId, player.uniqueId, CoroutineScope(Dispatchers.Main + SupervisorJob())) + clientManager.put(player, client) + listener.onQuit(createEvent(player)) + + assertEquals(0, clientManager.clients.size) + assertFalse { client.isActive } + } + + private fun createEvent(player: Player): PlayerQuitEvent { + return PlayerQuitEvent(player, mockk(), PlayerQuitEvent.QuitReason.DISCONNECTED) + } + + } + + private fun createPlayerMock(): Player { + val name = getRandomString() + val player = mockk(name) + every { player.name } returns name + every { player.uniqueId } returns UUID.randomUUID() + return player + } + + private fun createClient(player: Player) = + Client(pluginId, player.uniqueId, CoroutineScope(Dispatchers.Default + SupervisorJob())) +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/listener/VillagerListenerTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/listener/VillagerListenerTest.kt new file mode 100644 index 00000000..c0cf8fa3 --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/listener/VillagerListenerTest.kt @@ -0,0 +1,73 @@ +package io.github.distractic.bukkit.api.listener + +import io.github.distractic.bukkit.api.AbstractKoinTest +import io.github.distractic.bukkit.api.extension.namespacedKeyKeepJob +import io.github.distractic.bukkit.api.utils.getRandomString +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot +import org.bukkit.NamespacedKey +import org.bukkit.entity.Villager +import org.bukkit.event.entity.VillagerCareerChangeEvent +import org.bukkit.persistence.PersistentDataContainer +import org.bukkit.persistence.PersistentDataType +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import kotlin.test.* + +class VillagerListenerTest : AbstractKoinTest() { + + private lateinit var listener: VillagerListener + + @BeforeTest + override fun onBefore() { + super.onBefore() + listener = VillagerListener(plugin) + } + + @Nested + @DisplayName("Career change event") + inner class CareerChange { + + @Test + fun `cancel event when tag present in entity`() { + val villager = mockk(getRandomString()) + val container = mockk() + + val slotNamespaced = slot() + every { container.get(capture(slotNamespaced), PersistentDataType.BYTE) } returns 0.toByte() + every { villager.persistentDataContainer } returns container + + val event = VillagerCareerChangeEvent( + villager, + Villager.Profession.LEATHERWORKER, + VillagerCareerChangeEvent.ChangeReason.EMPLOYED + ) + event.isCancelled = false + listener.onChangeCareer(event) + assertEquals(namespacedKeyKeepJob(plugin), slotNamespaced.captured) + assertTrue { event.isCancelled } + } + + @Test + fun `not cancel event when tag present in entity`() { + val villager = mockk(getRandomString()) + + val container = mockk() + val slotNamespaced = slot() + every { container.get(capture(slotNamespaced), any>()) } returns null + every { villager.persistentDataContainer } returns container + + val event = VillagerCareerChangeEvent( + villager, + Villager.Profession.LEATHERWORKER, + VillagerCareerChangeEvent.ChangeReason.EMPLOYED + ) + event.isCancelled = true + listener.onChangeCareer(event) + assertEquals(namespacedKeyKeepJob(plugin), slotNamespaced.captured) + assertFalse { event.isCancelled } + } + + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImplTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImplTest.kt new file mode 100644 index 00000000..d03e7765 --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImplTest.kt @@ -0,0 +1,173 @@ +package io.github.distractic.bukkit.api.player + +import io.github.distractic.bukkit.api.AbstractKoinTest +import io.github.distractic.bukkit.api.player.exception.ClientNotFoundException +import io.github.distractic.bukkit.api.utils.getRandomString +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.test.runTest +import org.bukkit.entity.Player +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import java.util.* +import kotlin.test.* + +class ClientManagerImplTest : AbstractKoinTest() { + + private lateinit var clientManager: ClientManager + + @BeforeTest + override fun onBefore() { + super.onBefore() + clientManager = ClientManagerImpl() + } + + @Test + fun `has no clients when created`() { + assertEquals(0, clientManager.clients.size) + } + + @Nested + @DisplayName("Put client") + inner class Put { + + @Test + fun `save client linked to the name of the player`() = runTest { + val player = createPlayerMock() + val client = createClient(player) + clientManager.put(player, client) + + val clients = clientManager.clients + assertEquals(1, clients.size) + assertEquals(client, clients.values.first()) + assertEquals(player.name, clients.keys.first()) + } + + @Test + fun `save client returns the previous client instance`() = runTest { + val player = createPlayerMock() + val client = createClient(player) + assertNull(clientManager.put(player, client)) + + val client2 = createClient(player) + assertEquals(client, clientManager.put(player, client2)) + + assertEquals(client2, clientManager.getClient(player)) + } + + @Test + fun `save client if there is no instance already linked`() = runTest { + val player = createPlayerMock() + val client = createClient(player) + assertNull(clientManager.put(player, client)) + + val client2 = createClient(player) + assertEquals(client, clientManager.putIfAbsent(player, client2)) + + assertEquals(client, clientManager.getClient(player)) + } + } + + @Nested + @DisplayName("Remove client") + inner class Remove { + + @Test + fun `returns null when client for player is not linked`() = runTest { + assertNull(clientManager.removeClient(createPlayerMock())) + } + + @Test + fun `returns the instance of client linked to the player`() = runTest { + val player = createPlayerMock() + val client = createClient(player) + clientManager.put(player, client) + assertEquals(client, clientManager.removeClient(player)) + } + + } + + @Nested + @DisplayName("Contains player") + inner class Contains { + + @Test + fun `contains a player`() = runTest { + val player = createPlayerMock() + clientManager.put(player, createClient(player)) + assertTrue { clientManager.contains(player) } + + val player2 = createPlayerMock() + clientManager.put(player2, createClient(player2)) + assertTrue { clientManager.contains(player) } + assertTrue { clientManager.contains(player2) } + } + } + + @Nested + @DisplayName("Get client") + inner class Get { + + @Test + fun `retrieve from player`() = runTest { + val player = createPlayerMock() + assertThrows { + clientManager.getClient(player) + } + + val client = createClient(player) + clientManager.put(player, client) + assertEquals(client, clientManager.getClient(player)) + } + + @Test + fun `retrieve from player or null`() = runTest { + val player = createPlayerMock() + assertNull(clientManager.getClientOrNull(player)) + + val client = createClient(player) + clientManager.put(player, client) + assertEquals(client, clientManager.getClientOrNull(player)) + } + + @Test + fun `retrieve from name`() = runTest { + val player = createPlayerMock() + val name = player.name + assertThrows { + clientManager.getClient(name) + } + + val client = createClient(player) + clientManager.put(player, client) + assertEquals(client, clientManager.getClient(name)) + } + + @Test + fun `retrieve from name or null`() = runTest { + val player = createPlayerMock() + val name = player.name + assertNull(clientManager.getClientOrNull(name)) + + val client = createClient(player) + clientManager.put(player, client) + assertEquals(client, clientManager.getClientOrNull(name)) + } + + } + + private fun createPlayerMock(): Player { + val name = getRandomString() + val player = mockk(name) + every { player.name } returns name + every { player.uniqueId } returns UUID.randomUUID() + return player + } + + private fun createClient(player: Player) = + Client(pluginId, player.uniqueId, CoroutineScope(Dispatchers.Default + SupervisorJob())) +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/player/ClientTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/player/ClientTest.kt new file mode 100644 index 00000000..0aed53a7 --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/player/ClientTest.kt @@ -0,0 +1,52 @@ +package io.github.distractic.bukkit.api.player + +import io.github.distractic.bukkit.api.AbstractKoinTest +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.CoroutineScope +import org.bukkit.entity.Player +import org.junit.jupiter.api.assertThrows +import java.util.* +import kotlin.coroutines.EmptyCoroutineContext +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ClientTest : AbstractKoinTest() { + + @Test + fun `retrieve player instance not found returns null`() { + val client = Client(pluginId, UUID.randomUUID(), CoroutineScope(EmptyCoroutineContext)) + every { server.getPlayer(any()) } returns null + assertNull(client.player) + } + + @Test + fun `retrieve player instance found returns the instance`() { + val uuid = UUID.randomUUID() + val client = Client(pluginId, uuid, CoroutineScope(EmptyCoroutineContext)) + + val player = mockk() + every { server.getPlayer(uuid) } returns player + assertEquals(player, client.player) + } + + @Test + fun `require player instance not found throws an exception`() { + val client = Client(pluginId, UUID.randomUUID(), CoroutineScope(EmptyCoroutineContext)) + every { server.getPlayer(any()) } returns null + assertThrows { + client.requirePlayer() + } + } + + @Test + fun `require player instance found returns the instance`() { + val uuid = UUID.randomUUID() + val client = Client(pluginId, uuid, CoroutineScope(EmptyCoroutineContext)) + + val player = mockk() + every { server.getPlayer(uuid) } returns player + assertEquals(player, client.requirePlayer()) + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTaskTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTaskTest.kt new file mode 100644 index 00000000..60c6acec --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTaskTest.kt @@ -0,0 +1,456 @@ +package io.github.distractic.bukkit.api.schedule + +import io.github.distractic.bukkit.api.utils.getRandomString +import kotlinx.coroutines.* +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.minutes + +class SchedulerTaskTest { + + @Test + fun `no task when created`() { + assertTrue { SchedulerTask(scope(), 1.minutes).tasks.isEmpty() } + } + + @Nested + inner class Add { + + @Nested + inner class Index { + + @Test + fun `out of the list`() { + val scheduler = SchedulerTask( + scope(), + 10.milliseconds + ) + + assertThrows { + scheduler.addAtUnsafe(-1) {} + } + + assertThrows { + scheduler.addAtUnsafe(1) {} + } + + runBlocking { + scheduler.add {} + } + + assertThrows { + scheduler.addAtUnsafe(2) {} + } + } + + @Test + fun `during running`() = runBlocking { + val scheduler = SchedulerTask( + scope(), + 10.milliseconds + ) + scheduler.start() + + val body1: suspend SchedulerTask.Task.() -> Unit = { } + val id1 = getRandomString() + val task1 = scheduler.addAt(0, id1, body1) + assertEquals(id1, task1.id) + assertEquals(scheduler, task1.parent) + assertEquals(body1, task1.body) + assertEquals(listOf(task1), scheduler.tasks) + + val body2: suspend SchedulerTask.Task.() -> Unit = { } + val id2 = getRandomString() + val task2 = scheduler.addAt(1, id2, body2) + assertEquals(id2, task2.id) + assertEquals(scheduler, task2.parent) + assertEquals(body2, task2.body) + assertEquals(listOf(task1, task2), scheduler.tasks) + + val body3: suspend SchedulerTask.Task.() -> Unit = { } + val id3 = getRandomString() + val task3 = scheduler.addAt(1, id3, body3) + assertEquals(id3, task3.id) + assertEquals(scheduler, task3.parent) + assertEquals(body3, task3.body) + assertEquals(listOf(task1, task3, task2), scheduler.tasks) + } + + @Test + fun `during idle`() = runBlocking { + val scheduler = SchedulerTask( + scope(), + 10.milliseconds + ) + + val body1: suspend SchedulerTask.Task.() -> Unit = { } + val id1 = getRandomString() + val task1 = scheduler.addAtUnsafe(0, id1, body1) + assertEquals(id1, task1.id) + assertEquals(scheduler, task1.parent) + assertEquals(body1, task1.body) + assertEquals(listOf(task1), scheduler.tasks) + + val body2: suspend SchedulerTask.Task.() -> Unit = { } + val id2 = getRandomString() + val task2 = scheduler.addAtUnsafe(1, id2, body2) + assertEquals(id2, task2.id) + assertEquals(scheduler, task2.parent) + assertEquals(body2, task2.body) + assertEquals(listOf(task1, task2), scheduler.tasks) + + val body3: suspend SchedulerTask.Task.() -> Unit = { } + val id3 = getRandomString() + val task3 = scheduler.addAtUnsafe(0, id3, body3) + assertEquals(id3, task3.id) + assertEquals(scheduler, task3.parent) + assertEquals(body3, task3.body) + assertEquals(listOf(task3, task1, task2), scheduler.tasks) + } + } + + @Test + fun `during running`() = runBlocking { + val scheduler = SchedulerTask( + scope(), + 10.milliseconds + ) + scheduler.start() + + val body1: suspend SchedulerTask.Task.() -> Unit = { } + val id1 = getRandomString() + val task1 = scheduler.add(id1, body1) + assertEquals(id1, task1.id) + assertEquals(scheduler, task1.parent) + assertEquals(body1, task1.body) + assertEquals(listOf(task1), scheduler.tasks) + + val body2: suspend SchedulerTask.Task.() -> Unit = { } + val id2 = getRandomString() + val task2 = scheduler.add(id2, body2) + assertEquals(id2, task2.id) + assertEquals(scheduler, task2.parent) + assertEquals(body2, task2.body) + assertEquals(listOf(task1, task2), scheduler.tasks) + } + + @Test + fun `during idle`() = runBlocking { + val scheduler = SchedulerTask( + scope(), + 10.milliseconds + ) + + val body1: suspend SchedulerTask.Task.() -> Unit = { } + val id1 = getRandomString() + val task1 = scheduler.addUnsafe(id1, body1) + assertEquals(id1, task1.id) + assertEquals(scheduler, task1.parent) + assertEquals(body1, task1.body) + assertEquals(listOf(task1), scheduler.tasks) + + val body2: suspend SchedulerTask.Task.() -> Unit = { } + val id2 = getRandomString() + val task2 = scheduler.addUnsafe(id2, body2) + assertEquals(id2, task2.id) + assertEquals(scheduler, task2.parent) + assertEquals(body2, task2.body) + assertEquals(listOf(task1, task2), scheduler.tasks) + } + } + + @Nested + inner class Remove { + + @Nested + inner class Index { + + @Test + fun `out of the list`() { + val scheduler = SchedulerTask( + scope(), + 10.milliseconds + ) + + assertThrows { + scheduler.removeAtUnsafe(-1) + } + + assertThrows { + scheduler.removeAtUnsafe(0) + } + + runBlocking { + scheduler.add {} + } + + assertThrows { + scheduler.removeAtUnsafe(1) + } + } + + @Test + fun `during running`() = runBlocking { + val scheduler = SchedulerTask( + scope(), + 10.milliseconds + ) + + val task1 = scheduler.add { } + scheduler.add { } + val task3 = scheduler.add { } + scheduler.start() + + scheduler.removeAt(1) + assertEquals(listOf(task1, task3), scheduler.tasks) + scheduler.removeAt(1) + assertEquals(listOf(task1), scheduler.tasks) + scheduler.removeAt(0) + assertEquals(listOf(), scheduler.tasks) + } + + @Test + fun `during idle`() = runBlocking { + val scheduler = SchedulerTask( + scope(), + 10.milliseconds + ) + + val task1 = scheduler.add { } + val task2 = scheduler.add { } + scheduler.add { } + scheduler.start() + + scheduler.removeAt(2) + assertEquals(listOf(task1, task2), scheduler.tasks) + scheduler.removeAt(0) + assertEquals(listOf(task2), scheduler.tasks) + scheduler.removeAt(0) + assertEquals(listOf(), scheduler.tasks) + } + } + + @Test + fun `during running`() = runBlocking { + val scheduler = SchedulerTask( + scope(), + 10.milliseconds + ) + scheduler.start() + + val task1 = scheduler.add {} + val task2 = scheduler.add {} + + assertTrue { scheduler.remove(task1.id) } + assertEquals(listOf(task2), scheduler.tasks) + assertTrue { scheduler.remove(task2.id) } + assertEquals(listOf(), scheduler.tasks) + } + + @Test + fun `during idle`() = runBlocking { + val scheduler = SchedulerTask( + scope(), + 10.milliseconds + ) + + val task1 = scheduler.add {} + val task2 = scheduler.add {} + + assertTrue { scheduler.removeUnsafe(task1.id) } + assertEquals(listOf(task2), scheduler.tasks) + assertTrue { scheduler.removeUnsafe(task2.id) } + assertEquals(listOf(), scheduler.tasks) + } + } + + @Nested + @DisplayName("Delay before first execution") + inner class DelayBefore { + + @Test + fun `enable option`() = runBlocking { + var isExecuted = false + val scheduler = SchedulerTask( + scope(), + 1.minutes, + delayBefore = true + ) + + scheduler.addUnsafe { + isExecuted = true + } + + scheduler.start() + delay(100) + assertFalse { isExecuted } + } + + @Test + fun `disable option`() = runBlocking { + var isExecuted = false + val scheduler = SchedulerTask( + scope(), + 1.minutes, + delayBefore = false + ) + + scheduler.addUnsafe { + isExecuted = true + } + + scheduler.start() + delay(100) + assertTrue { isExecuted } + } + } + + @Nested + @DisplayName("Safe in case of error") + inner class Error { + + @Test + fun `continue schedule if error into the body`() = runBlocking { + var counter = 0 + val scheduler = SchedulerTask( + scope(), + 10.milliseconds + ) + + scheduler.addUnsafe { + counter++ + error("Error in body") + } + + scheduler.start() + val countDownLatch = CountDownLatch(10) + while(counter <= 1) { + assertFalse { countDownLatch.await(10, TimeUnit.MILLISECONDS) } + countDownLatch.countDown() + } + + assertTrue { counter >= 2 } + + } + } + + @Nested + inner class Run { + + @Test + fun `body is executed several times`() = runBlocking { + var counter = 0 + val scheduler = SchedulerTask( + scope(), + 10.milliseconds + ) + scheduler.addUnsafe { counter++ } + + scheduler.start() + delay(100) + assertTrue { counter in 2..10 } + } + + @Test + fun `is running enabled when the scheduler is executed`() { + val scheduler = SchedulerTask( + scope(), + 10.milliseconds + ) + + assertFalse { scheduler.running } + scheduler.start() + assertTrue { scheduler.running } + } + + @Test + fun `can be stop and re-start`() = runBlocking { + var counter = 0 + val scheduler = SchedulerTask( + scope(), + 10.milliseconds + ) + scheduler.addUnsafe { counter++ } + + scheduler.start() + scheduler.cancel() + assertFalse { scheduler.running } + + counter = 0 + scheduler.start() + assertTrue { scheduler.running } + delay(50) + assertTrue { counter > 1 } + } + + @Test + fun `coroutine to run scheduler is the children of the coroutine scope`() { + val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + val scheduler = SchedulerTask( + scope, + 10.milliseconds + ) + + scheduler.start() + scope.cancel("Cancel parent") + assertFalse { scheduler.running } + } + + } + + @Nested + inner class Cancel { + + @Test + fun `cancel will change the state of the scheduler`() = runBlocking { + val scheduler = SchedulerTask( + scope(), + 10.milliseconds + ) + + scheduler.start() + assertTrue { scheduler.running } + scheduler.cancel() + assertFalse { scheduler.running } + } + + @Test + fun `cancel when no task`() = runBlocking { + val scheduler = SchedulerTask( + scope(), + 10.milliseconds, + stopWhenNoTask = true + ) + val task = scheduler.add { } + scheduler.start() + assertTrue { scheduler.running } + scheduler.remove(task.id) + assertFalse { scheduler.running } + } + + @Test + fun `keep alive despite missing task`() = runBlocking { + val scheduler = SchedulerTask( + scope(), + 10.milliseconds, + stopWhenNoTask = false + ) + val task = scheduler.add { } + scheduler.start() + assertTrue { scheduler.running } + scheduler.remove(task.id) + assertTrue { scheduler.running } + } + + } + + private fun scope() = CoroutineScope(Dispatchers.IO + SupervisorJob()) +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/utils/Generator.kt b/src/test/kotlin/io/github/distractic/bukkit/api/utils/Generator.kt new file mode 100644 index 00000000..8df39940 --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/utils/Generator.kt @@ -0,0 +1,23 @@ +package io.github.distractic.bukkit.api.utils + +import org.bukkit.Location +import org.bukkit.World +import java.util.* +import kotlin.random.Random + +val stringGenerator = generateSequence { UUID.randomUUID().toString() }.distinct().iterator() + +fun getRandomString() = stringGenerator.next() + +const val LIMIT_RANDOM_COORDINATE = 1000.0 + +fun createRandomLocation(world: World? = null): Location { + return Location( + world, + Random.nextDouble(LIMIT_RANDOM_COORDINATE), + Random.nextDouble(LIMIT_RANDOM_COORDINATE), + Random.nextDouble(LIMIT_RANDOM_COORDINATE), + Random.nextDouble(LIMIT_RANDOM_COORDINATE).toFloat(), + Random.nextDouble(LIMIT_RANDOM_COORDINATE).toFloat() + ) +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/utils/LocationTestUtils.kt b/src/test/kotlin/io/github/distractic/bukkit/api/utils/LocationTestUtils.kt new file mode 100644 index 00000000..aaff2075 --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/utils/LocationTestUtils.kt @@ -0,0 +1,15 @@ +package io.github.distractic.bukkit.api.utils + +import org.bukkit.Location +import kotlin.test.assertEquals + +fun assertEqualsLocation(loc1: Location, loc2: Location) { + with(loc1) { + assertEquals(world, loc2.world) + assertEquals(x, loc2.x) + assertEquals(y, loc2.y) + assertEquals(z, loc2.z) + assertEquals(yaw, loc2.yaw) + assertEquals(pitch, loc2.pitch) + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/world/CuboidTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/world/CuboidTest.kt new file mode 100644 index 00000000..8c8275bf --- /dev/null +++ b/src/test/kotlin/io/github/distractic/bukkit/api/world/CuboidTest.kt @@ -0,0 +1,392 @@ +package io.github.distractic.bukkit.api.world + +import io.github.distractic.bukkit.api.extension.minMax +import io.github.distractic.bukkit.api.utils.assertEqualsLocation +import io.github.distractic.bukkit.api.world.exception.WorldDifferentException +import io.mockk.every +import io.mockk.mockk +import org.bukkit.Location +import org.bukkit.World +import org.bukkit.block.Block +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import java.util.* +import kotlin.random.Random +import kotlin.test.* + +class CuboidTest { + + @Nested + @DisplayName("Create instance") + inner class CreateInstance { + + @Nested + @DisplayName("From 2 locations") + inner class FromLocations { + + @Test + fun `with different world`() { + val world1 = mockk() + val world2 = mockk() + val location1 = Location(world1, .0, .0, .0) + val location2 = Location(world2, .0, .0, .0) + val exception = assertThrows { + Cuboid(location1, location2) + } + assertEquals(world1, exception.world1) + assertEquals(world2, exception.world2) + } + + @Test + fun `with minimal and maximal coordinates already separated`() { + val world = createMockWorld() + val randomCoords = createRandomPositionsOrdered() + val location1 = Location(world, randomCoords[0], randomCoords[1], randomCoords[2]) + val location2 = Location(world, randomCoords[3], randomCoords[4], randomCoords[5]) + + val cuboid = Cuboid(location1, location2) + assertEqualsLocation(location1, cuboid.startLocation) + assertEqualsLocation(location2, cuboid.endLocation) + } + + @Test + fun `minimal and maximal coordinates mixed`() { + val world = createMockWorld() + val randomCoords = createRandomPositionsOrdered() + val location1 = Location(world, randomCoords[0], randomCoords[4], randomCoords[2]) + val location2 = Location(world, randomCoords[3], randomCoords[1], randomCoords[5]) + + val cuboid = Cuboid(location1, location2) + val startLocation = cuboid.startLocation + val endLocation = cuboid.endLocation + + assertEquals(location1.x, startLocation.x) + assertEquals(location2.y, startLocation.y) + assertEquals(location1.z, startLocation.z) + + assertEquals(location2.x, endLocation.x) + assertEquals(location1.y, endLocation.y) + assertEquals(location2.z, endLocation.z) + } + } + } + + @Nested + @DisplayName("Get world") + inner class GetWorld { + + @Test + fun `is linked to the location's world instance from constructor`() { + val world = mockk() + val location1 = Location(world, .0, .0, .0) + val location2 = Location(world, .0, .0, .0) + val cuboid = Cuboid(location1, location2) + assertEquals(world, cuboid.world) + } + + @Test + fun `is null if location's world instance is null`() { + val location1 = Location(null, .0, .0, .0) + val location2 = Location(null, .0, .0, .0) + val cuboid = Cuboid(location1, location2) + assertNull(cuboid.world) + } + + @Test + fun `is not linked to the location's world instance if changed`() { + val world = mockk() + val location1 = Location(world, .0, .0, .0) + val location2 = Location(world, .0, .0, .0) + val cuboid = Cuboid(location1, location2) + assertEquals(world, cuboid.world) + + location1.world = null + assertEquals(world, cuboid.world) + + location2.world = null + assertEquals(world, cuboid.world) + } + } + + @Nested + @DisplayName("Set world") + inner class SetWorld { + + @Test + fun `change the world of start and end location`() { + val location1 = Location(null, .0, .0, .0) + val location2 = Location(null, .0, .0, .0) + val cuboid = Cuboid(location1, location2) + assertNull(cuboid.world) + + val world = mockk() + cuboid.world = world + assertEquals(world, cuboid.world) + assertEquals(world, cuboid.startLocation.world) + assertEquals(world, cuboid.endLocation.world) + } + } + + @Nested + @DisplayName("Contains location") + inner class ContainsLocation { + + private lateinit var world: World + private lateinit var location1: Location + private lateinit var location2: Location + private lateinit var cuboid: Cuboid + + @BeforeTest + fun onBefore() { + world = createMockWorld() + location1 = Location(world, -100.0, -100.0, -100.0) + location2 = Location(world, 100.0, 100.0, 100.0) + cuboid = Cuboid(location1, location2) + } + + @Test + fun `location is in bound of minimal coordinate`() { + assertTrue { cuboid.contains(location1) } + } + + @Test + fun `location is in bound of maximal coordinate`() { + assertTrue { cuboid.contains(location2) } + } + + @Test + fun `location is in bound but world is different`() { + val location = Location(mockk(), location1.x, location1.y, location1.z) + assertFalse { cuboid.contains(location) } + } + + @Test + fun `location between bounds`() { + fun getMiddle(min: Double, max: Double) = min + (max - min) + assertTrue { + cuboid.contains( + Location( + world, + getMiddle(location1.x, location2.x), + getMiddle(location1.y, location2.y), + getMiddle(location1.z, location2.z) + ) + ) + } + } + + @Nested + @DisplayName("X out of bounds") + inner class XOut { + + @Test + fun `inferior than minimal`() { + assertFalse { + cuboid.contains( + location1.add(-1.0, 0.0, 0.0) + ) + } + } + + @Test + fun `superior than maximal`() { + assertFalse { + cuboid.contains( + location2.add(1.0, 0.0, 0.0) + ) + } + } + } + + @Nested + @DisplayName("Y out of bounds") + inner class YOut { + + @Test + fun `location has y is inferior than minimal`() { + assertFalse { + cuboid.contains( + location1.add(0.0, -1.0, 0.0) + ) + } + } + + @Test + fun `location has y is superior than maximal`() { + assertFalse { + cuboid.contains( + location2.add(0.0, 1.0, 0.0) + ) + } + } + } + + @Nested + @DisplayName("Z out of bounds") + inner class ZOut { + + @Test + fun `location has z is inferior than minimal`() { + assertFalse { + cuboid.contains( + location1.add(0.0, 0.0, -1.0) + ) + } + } + + @Test + fun `location has z is superior than maximal`() { + assertFalse { + cuboid.contains( + location2.add(0.0, 0.0, 1.0) + ) + } + } + } + } + + @Nested + @DisplayName("Sequence generate") + inner class Sequence { + + @Nested + @DisplayName("Location") + inner class LocationSequence { + + @Test + fun `give only one location if min and max coordinate are equals`() { + val world = createMockWorld() + + val blockPosition = Random.nextInt(10000) + val position = blockPosition.toDouble() + val location1 = Location(world, position, position, position) + val location2 = Location(world, position, position, position) + val cuboid = Cuboid(location1, location2) + + val locations = cuboid.locationSequence().toList() + assertEquals(1, locations.size) + + val location = locations.first() + assertEquals(location.world, world) + assertEquals(location.blockX, blockPosition) + assertEquals(location.blockY, blockPosition) + assertEquals(location.blockZ, blockPosition) + } + + @Test + fun `give all locations if min and max coordinate are not equals`() { + val world = createMockWorld() + + val bound = 10.0 + + val location1 = + Location(world, Random.nextDouble(bound), Random.nextDouble(bound), Random.nextDouble(bound)) + val location2 = + Location(world, Random.nextDouble(bound), Random.nextDouble(bound), Random.nextDouble(bound)) + + val cuboid = Cuboid(location1, location2) + + val mocks = mutableListOf() + for (x in cuboid.startLocation.blockX..cuboid.endLocation.blockX) { + for (y in cuboid.startLocation.blockY..cuboid.endLocation.blockY) { + for (z in cuboid.startLocation.blockZ..cuboid.endLocation.blockZ) { + mocks += Location(world, x.toDouble(), y.toDouble(), z.toDouble()) + } + } + } + + val locations = cuboid.locationSequence().toList() + assertEquals(mocks, locations) + } + } + + @Nested + @DisplayName("Block") + inner class BlockSequence { + + @Test + fun `give only one block if min and max coordinate are equals`() { + val world = createMockWorld() + val blockMock = mockk() + + val blockPosition = Random.nextInt(10000) + val position = blockPosition.toDouble() + + every { + world.getBlockAt( + Location( + world, + position, + position, + position + ) + ) + } returns blockMock + + val location1 = Location(world, position, position, position) + val location2 = Location(world, position, position, position) + + val cuboid = Cuboid(location1, location2) + + val blocks = cuboid.blockSequence().toList() + assertEquals(1, blocks.size) + + val block = blocks.first() + assertEquals(blockMock, block) + } + + @Test + fun `give all locations if min and max coordinate are not equals`() { + val world = createMockWorld() + + val bound = 10.0 + + val location1 = + Location(world, Random.nextDouble(bound), Random.nextDouble(bound), Random.nextDouble(bound)) + val location2 = + Location(world, Random.nextDouble(bound), Random.nextDouble(bound), Random.nextDouble(bound)) + + val cuboid = Cuboid(location1, location2) + + val mocks = mutableListOf() + for (x in cuboid.startLocation.blockX..cuboid.endLocation.blockX) { + for (y in cuboid.startLocation.blockY..cuboid.endLocation.blockY) { + for (z in cuboid.startLocation.blockZ..cuboid.endLocation.blockZ) { + val blockMock = mockk() + val location = Location(world, x.toDouble(), y.toDouble(), z.toDouble()) + every { world.getBlockAt(location) } returns blockMock + mocks += blockMock + } + } + } + + val blocks = cuboid.blockSequence().toList() + assertEquals(mocks, blocks) + } + } + } + + private fun createMockWorld(): World { + val world = mockk() + every { world.uid } returns UUID.randomUUID() + return world + } + + private fun createRandomPositionsOrdered(): DoubleArray { + val bound = 1000.0 + val (minX, maxX) = minMax( + Random.nextDouble(-bound, bound), + Random.nextDouble(-bound, bound) + ) + val (minY, maxY) = minMax( + Random.nextDouble(-bound, bound), + Random.nextDouble(-bound, bound) + ) + val (minZ, maxZ) = minMax( + Random.nextDouble(-bound, bound), + Random.nextDouble(-bound, bound) + ) + return doubleArrayOf(minX, minY, minZ, maxX, maxY, maxZ) + } +} From 2e16d0f982d45862bfb000d11bc322a7b005d725 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 2 Oct 2022 22:35:58 -0400 Subject: [PATCH 037/143] chore(deps): bump jvm from 1.7.10 to 1.7.20 (#12) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 3cd0e45b..4523d30a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("jvm") version "1.7.10" + kotlin("jvm") version "1.7.20" kotlin("plugin.serialization") version "1.7.10" id("org.jetbrains.dokka") version "1.7.10" id("com.github.johnrengelman.shadow") version "7.1.2" From 2b29336ed35b716fdfb2dcaebfd0e618d82924c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 2 Oct 2022 22:36:35 -0400 Subject: [PATCH 038/143] chore(deps): bump plugin.serialization from 1.7.10 to 1.7.20 (#11) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Distractic <46402441+Distractic@users.noreply.github.com> --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 4523d30a..6a5d886d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ plugins { kotlin("jvm") version "1.7.20" - kotlin("plugin.serialization") version "1.7.10" + kotlin("plugin.serialization") version "1.7.20" id("org.jetbrains.dokka") version "1.7.10" id("com.github.johnrengelman.shadow") version "7.1.2" id("net.researchgate.release") version "3.0.1" From 91944ca656835fcb43f76e15ac56c6a208c72c2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 2 Oct 2022 22:36:47 -0400 Subject: [PATCH 039/143] chore(deps): bump gradle/gradle-build-action from 2.2.5 to 2.3.2 (#10) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/Check.yml | 4 ++-- .github/workflows/CheckDependabot.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/Check.yml b/.github/workflows/Check.yml index a1b19aaa..99f26163 100644 --- a/.github/workflows/Check.yml +++ b/.github/workflows/Check.yml @@ -24,11 +24,11 @@ jobs: jdk: 17 - name: Build - uses: gradle/gradle-build-action@v2.2.5 + uses: gradle/gradle-build-action@v2.3.2 with: arguments: build -x test - name: Test - uses: gradle/gradle-build-action@v2.2.5 + uses: gradle/gradle-build-action@v2.3.2 with: arguments: test \ No newline at end of file diff --git a/.github/workflows/CheckDependabot.yml b/.github/workflows/CheckDependabot.yml index f58c94dd..f42b401d 100644 --- a/.github/workflows/CheckDependabot.yml +++ b/.github/workflows/CheckDependabot.yml @@ -18,11 +18,11 @@ jobs: jdk: 17 - name: Build - uses: gradle/gradle-build-action@v2.2.5 + uses: gradle/gradle-build-action@v2.3.2 with: arguments: build -x test - name: Test - uses: gradle/gradle-build-action@v2.2.5 + uses: gradle/gradle-build-action@v2.3.2 with: arguments: test \ No newline at end of file From 5f41b78754201f0d0299880f9bca4eabc62300df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 2 Oct 2022 22:37:01 -0400 Subject: [PATCH 040/143] chore(deps): bump net.researchgate.release from 3.0.1 to 3.0.2 (#8) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 6a5d886d..7aacaf82 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ plugins { kotlin("plugin.serialization") version "1.7.20" id("org.jetbrains.dokka") version "1.7.10" id("com.github.johnrengelman.shadow") version "7.1.2" - id("net.researchgate.release") version "3.0.1" + id("net.researchgate.release") version "3.0.2" `maven-publish` } From 8d122a0071050d353e2a00d025cf9eea956e2c21 Mon Sep 17 00:00:00 2001 From: Quentin Date: Thu, 20 Jul 2023 19:04:17 +0200 Subject: [PATCH 041/143] Migrate project from @Distractic bukkit-api --- build.gradle.kts | 31 +- .../github/rushyverse}/api/APIPlugin.kt | 2 +- .../github/rushyverse}/api/Plugin.kt | 34 +- .../com/github/rushyverse/api/SharedMemory.kt | 14 + .../exception/SilentCancellationException.kt | 2 +- .../api/delegate/DelegatePlayer.kt | 4 +- .../rushyverse}/api/delegate/DelegateWorld.kt | 4 +- .../api/extension/ConfigurationSectionExt.kt | 25 ++ .../api/extension/_CommandSender.kt | 2 +- .../rushyverse}/api/extension/_Comparable.kt | 2 +- .../rushyverse}/api/extension/_Component.kt | 2 +- .../api/extension/_CoroutineScope.kt | 4 +- .../rushyverse}/api/extension/_Duration.kt | 2 +- .../rushyverse}/api/extension/_Event.kt | 2 +- .../rushyverse}/api/extension/_ItemStack.kt | 2 +- .../rushyverse}/api/extension/_JavaPlugin.kt | 7 +- .../rushyverse}/api/extension/_Listener.kt | 2 +- .../rushyverse}/api/extension/_Location.kt | 7 +- .../api/extension/_MerchantRecipe.kt | 2 +- .../api/extension/_PersistentDataHolder.kt | 2 +- .../rushyverse}/api/extension/_Player.kt | 2 +- .../api/extension/_PlayerProfile.kt | 2 +- .../rushyverse}/api/extension/_Runnable.kt | 2 +- .../rushyverse/api/extension/_String.kt | 208 ++++++++++ .../rushyverse}/api/extension/_Villager.kt | 2 +- .../rushyverse}/api/extension/_World.kt | 2 +- .../github/rushyverse/api/game/GameData.kt | 8 + .../github/rushyverse/api/game/GameState.kt | 10 + .../rushyverse/api/game/SharedGameData.kt | 56 +++ .../api/game/stats/KillableStats.kt | 15 + .../github/rushyverse/api/game/stats/Stats.kt | 5 + .../api/game/stats/WinnableStats.kt | 14 + .../rushyverse/api/game/team/TeamType.kt | 51 +++ .../rushyverse}/api/item/CraftBuilder.kt | 6 +- .../exception/CraftResultMissingException.kt | 2 +- .../rushyverse}/api/koin/CraftContext.kt | 2 +- .../api/listener/PlayerListener.kt | 38 +- .../api/listener/VillagerListener.kt | 4 +- .../github/rushyverse/api/player/Client.kt | 44 ++ .../rushyverse}/api/player/ClientManager.kt | 2 +- .../api/player/ClientManagerImpl.kt | 4 +- .../api/player/PluginClientEvents.kt | 31 ++ .../exception/ClientAlreadyExistsException.kt | 2 +- .../exception/ClientNotFoundException.kt | 2 +- .../exception/PlayerNotFoundException.kt | 2 +- .../player/exception/PlayerQuitException.kt | 2 +- .../api/schedule/AbstractScheduler.kt | 2 +- .../rushyverse}/api/schedule/Scheduler.kt | 2 +- .../rushyverse}/api/schedule/SchedulerTask.kt | 2 +- .../ResourceBundleNotRegisteredException.kt | 11 + .../ResourceBundleTranslationsProvider.kt | 85 ++++ .../api/translation/SupportedLanguage.kt | 13 + .../api/translation/TranslationsProvider.kt | 43 ++ .../com/github/rushyverse/api/world/Cuboid.kt | 39 ++ .../com/github/rushyverse/api/world/Pos.kt | 36 ++ .../exception/WorldDifferentException.kt | 2 +- .../world/exception/WorldNotFoundException.kt | 2 +- .../bukkit/api/extension/_String.kt | 106 ----- .../distractic/bukkit/api/player/Client.kt | 26 -- .../distractic/bukkit/api/world/Cuboid.kt | 103 ----- src/main/resources/api_translate.properties | 10 + .../resources/api_translate_en_GB.properties | 0 .../resources/api_translate_es_ES.properties | 0 .../resources/api_translate_fr_FR.properties | 14 + src/main/resources/plugin.yml | 6 +- .../rushyverse}/api/AbstractKoinTest.kt | 11 +- .../api/delegate/DelegateWorldTest.kt | 4 +- .../api/delegate/PlayerWorldTest.kt | 4 +- .../api/extension/CommandSenderExtTest.kt | 4 +- .../api/extension/ComparableExtTest.kt | 2 +- .../api/extension/CoroutineScopeExt.kt | 4 +- .../api/extension/DurationExtTest.kt | 2 +- .../rushyverse}/api/extension/EventExtTest.kt | 2 +- .../api/extension/ItemStackExtTest.kt | 2 +- .../api/extension/JavaPluginExtTest.kt | 2 +- .../api/extension/LocationExtTest.kt | 2 +- .../api/extension/MerchantRecipeExtTest.kt | 2 +- .../api/extension/PersistentDataHolderTest.kt | 2 +- .../api/extension/PlayerExtTest.kt | 2 +- .../api/extension/RunnableExtTest.kt | 2 +- .../api/extension/StringExtTest.kt | 2 +- .../api/extension/VillagerExtTest.kt | 8 +- .../rushyverse}/api/extension/WorldExtTest.kt | 2 +- .../rushyverse}/api/koin/CraftContextTest.kt | 2 +- .../api/listener/PlayerListenerTest.kt | 2 +- .../api/listener/VillagerListenerTest.kt | 2 +- .../api/player/ClientManagerImplTest.kt | 2 +- .../rushyverse}/api/player/ClientTest.kt | 2 +- .../api/schedule/SchedulerTaskTest.kt | 2 +- .../github/rushyverse}/api/utils/Generator.kt | 2 +- .../api/utils/LocationTestUtils.kt | 2 +- .../github/rushyverse/api/world/CuboidTest.kt | 6 + .../distractic/bukkit/api/world/CuboidTest.kt | 392 ------------------ 93 files changed, 905 insertions(+), 740 deletions(-) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/APIPlugin.kt (76%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/Plugin.kt (53%) create mode 100644 src/main/kotlin/com/github/rushyverse/api/SharedMemory.kt rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/coroutine/exception/SilentCancellationException.kt (87%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/delegate/DelegatePlayer.kt (85%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/delegate/DelegateWorld.kt (85%) create mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/ConfigurationSectionExt.kt rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/_CommandSender.kt (96%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/_Comparable.kt (87%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/_Component.kt (98%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/_CoroutineScope.kt (96%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/_Duration.kt (96%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/_Event.kt (89%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/_ItemStack.kt (99%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/_JavaPlugin.kt (92%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/_Listener.kt (98%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/_Location.kt (92%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/_MerchantRecipe.kt (97%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/_PersistentDataHolder.kt (91%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/_Player.kt (96%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/_PlayerProfile.kt (94%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/_Runnable.kt (96%) create mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/_String.kt rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/_Villager.kt (96%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/_World.kt (97%) create mode 100644 src/main/kotlin/com/github/rushyverse/api/game/GameData.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/game/GameState.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/game/SharedGameData.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/game/stats/KillableStats.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/game/stats/Stats.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/game/stats/WinnableStats.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/item/CraftBuilder.kt (97%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/item/exception/CraftResultMissingException.kt (75%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/koin/CraftContext.kt (99%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/listener/PlayerListener.kt (63%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/listener/VillagerListener.kt (87%) create mode 100644 src/main/kotlin/com/github/rushyverse/api/player/Client.kt rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/player/ClientManager.kt (98%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/player/ClientManagerImpl.kt (94%) create mode 100644 src/main/kotlin/com/github/rushyverse/api/player/PluginClientEvents.kt rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/player/exception/ClientAlreadyExistsException.kt (73%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/player/exception/ClientNotFoundException.kt (71%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/player/exception/PlayerNotFoundException.kt (72%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/player/exception/PlayerQuitException.kt (82%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/schedule/AbstractScheduler.kt (95%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/schedule/Scheduler.kt (92%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/schedule/SchedulerTask.kt (99%) create mode 100644 src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleNotRegisteredException.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationsProvider.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/translation/TranslationsProvider.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/world/Cuboid.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/world/Pos.kt rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/world/exception/WorldDifferentException.kt (81%) rename src/main/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/world/exception/WorldNotFoundException.kt (72%) delete mode 100644 src/main/kotlin/io/github/distractic/bukkit/api/extension/_String.kt delete mode 100644 src/main/kotlin/io/github/distractic/bukkit/api/player/Client.kt delete mode 100644 src/main/kotlin/io/github/distractic/bukkit/api/world/Cuboid.kt create mode 100644 src/main/resources/api_translate.properties create mode 100644 src/main/resources/api_translate_en_GB.properties create mode 100644 src/main/resources/api_translate_es_ES.properties create mode 100644 src/main/resources/api_translate_fr_FR.properties rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/AbstractKoinTest.kt (81%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/delegate/DelegateWorldTest.kt (89%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/delegate/PlayerWorldTest.kt (89%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/CommandSenderExtTest.kt (89%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/ComparableExtTest.kt (96%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/CoroutineScopeExt.kt (93%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/DurationExtTest.kt (98%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/EventExtTest.kt (96%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/ItemStackExtTest.kt (99%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/JavaPluginExtTest.kt (99%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/LocationExtTest.kt (98%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/MerchantRecipeExtTest.kt (98%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/PersistentDataHolderTest.kt (92%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/PlayerExtTest.kt (98%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/RunnableExtTest.kt (85%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/StringExtTest.kt (98%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/VillagerExtTest.kt (93%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/extension/WorldExtTest.kt (98%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/koin/CraftContextTest.kt (99%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/listener/PlayerListenerTest.kt (98%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/listener/VillagerListenerTest.kt (98%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/player/ClientManagerImplTest.kt (99%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/player/ClientTest.kt (97%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/schedule/SchedulerTaskTest.kt (99%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/utils/Generator.kt (93%) rename src/test/kotlin/{io/github/distractic/bukkit => com/github/rushyverse}/api/utils/LocationTestUtils.kt (88%) create mode 100644 src/test/kotlin/com/github/rushyverse/api/world/CuboidTest.kt delete mode 100644 src/test/kotlin/io/github/distractic/bukkit/api/world/CuboidTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 7aacaf82..7587fafa 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,17 +1,18 @@ plugins { - kotlin("jvm") version "1.7.20" - kotlin("plugin.serialization") version "1.7.20" - id("org.jetbrains.dokka") version "1.7.10" + kotlin("jvm") version "1.8.22" + kotlin("plugin.serialization") version "1.8.22" + id("org.jetbrains.dokka") version "1.8.20" id("com.github.johnrengelman.shadow") version "7.1.2" id("net.researchgate.release") version "3.0.2" `maven-publish` + `java-library` } repositories { mavenCentral() - maven { - url = uri("https://repo.papermc.io/repository/maven-public/") - } + maven("https://repo.papermc.io/repository/maven-public/") + maven("https://repo.codemc.org/repository/maven-public/") + maven("https://jitpack.io") } val coroutineVersion: String by project @@ -25,21 +26,37 @@ val slf4jVersion: String by project dependencies { implementation(kotlin("stdlib")) + implementation(kotlin("stdlib-jdk8")) implementation(kotlin("reflect")) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutineVersion") implementation("io.github.microutils:kotlin-logging:$loggingVersion") + // Injection framework implementation("io.insert-koin:koin-core:$koinVersion") implementation("io.insert-koin:koin-logger-slf4j:$koinVersion") + // MC coroutine framework implementation("com.github.shynixn.mccoroutine:mccoroutine-bukkit-api:$mccoroutineVersion") implementation("com.github.shynixn.mccoroutine:mccoroutine-bukkit-core:$mccoroutineVersion") + // Minecraft server framework compileOnly("io.papermc.paper:paper-api:$paperVersion") + // Scoreboard framework + implementation("fr.mrmicky:fastboard:2.0.0") + + // CommandAPI framework + // The CommandAPI dependency used for Bukkit and it's forks + api("dev.jorel:commandapi-bukkit-core:9.0.3") + // Due to all functions available in the kotlindsl being inlined, we only need this dependency at compile-time + api("dev.jorel:commandapi-bukkit-kotlin:9.0.3") + + api("com.github.Rushyverse:core:6ae31a9250") + + // Tests testImplementation(kotlin("test-junit5")) - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutineVersion") + testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutineVersion") testImplementation("io.papermc.paper:paper-api:$paperVersion") testImplementation("org.junit.jupiter:junit-jupiter-params:$junitVersion") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitVersion") diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/APIPlugin.kt b/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt similarity index 76% rename from src/main/kotlin/io/github/distractic/bukkit/api/APIPlugin.kt rename to src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt index 76e58eee..5dfc6d18 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/APIPlugin.kt +++ b/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api +package com.github.rushyverse.api import org.bukkit.plugin.java.JavaPlugin diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/Plugin.kt b/src/main/kotlin/com/github/rushyverse/api/Plugin.kt similarity index 53% rename from src/main/kotlin/io/github/distractic/bukkit/api/Plugin.kt rename to src/main/kotlin/com/github/rushyverse/api/Plugin.kt index 2e139bc9..49706033 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/Plugin.kt +++ b/src/main/kotlin/com/github/rushyverse/api/Plugin.kt @@ -1,18 +1,16 @@ -package io.github.distractic.bukkit.api +package com.github.rushyverse.api import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin -import io.github.distractic.bukkit.api.extension.registerListener -import io.github.distractic.bukkit.api.koin.CraftContext -import io.github.distractic.bukkit.api.koin.loadModule -import io.github.distractic.bukkit.api.listener.PlayerListener -import io.github.distractic.bukkit.api.listener.VillagerListener -import io.github.distractic.bukkit.api.player.Client -import io.github.distractic.bukkit.api.player.ClientManager -import io.github.distractic.bukkit.api.player.ClientManagerImpl +import com.github.rushyverse.api.extension.registerListener +import com.github.rushyverse.api.koin.* +import com.github.rushyverse.api.listener.* +import com.github.rushyverse.api.player.* +import com.github.rushyverse.api.translation.* import org.bukkit.Bukkit import org.bukkit.entity.Player import org.koin.core.module.Module import org.koin.dsl.bind +import java.util.* /** * Abstract plugin with the necessary component to create a plugin. @@ -20,6 +18,11 @@ import org.koin.dsl.bind public abstract class Plugin : SuspendingJavaPlugin() { public abstract val id: String + public abstract val clientEvents: PluginClientEvents + + public companion object { + public const val BUNDLE_API: String = "api_translate" + } override suspend fun onEnableAsync() { super.onEnableAsync() @@ -31,7 +34,7 @@ public abstract class Plugin : SuspendingJavaPlugin() { registerListener { VillagerListener(this) } } - protected inline fun modulePlugin(): Module = loadModule(id) { + protected inline fun modulePlugin(): Module = loadModule(id) { single { this@Plugin } single { this@Plugin as T } } @@ -42,6 +45,7 @@ public abstract class Plugin : SuspendingJavaPlugin() { protected open fun moduleBukkit(): Module = loadModule(id) { single { Bukkit.getServer() } + single { getLogger() } } override suspend fun onDisableAsync() { @@ -55,4 +59,14 @@ public abstract class Plugin : SuspendingJavaPlugin() { * @return C The instance of the client. */ public abstract fun createClient(player: Player): Client + + /** + * Create a translation provider to provide translations for the [supported languages][SupportedLanguage]. + * @param bundles Bundles to load. + * @return New translation provider. + */ + protected open suspend fun createTranslationsProvider(): ResourceBundleTranslationsProvider = + ResourceBundleTranslationsProvider().apply { + registerResourceBundleForSupportedLocales(com.github.rushyverse.api.Plugin.Companion.BUNDLE_API, ResourceBundle::getBundle) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/SharedMemory.kt b/src/main/kotlin/com/github/rushyverse/api/SharedMemory.kt new file mode 100644 index 00000000..932ae71a --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/SharedMemory.kt @@ -0,0 +1,14 @@ +package com.github.rushyverse.api + +import com.github.rushyverse.api.game.SharedGameData + +/** + * Ready to use Singleton object that represents a shared memory space that facilitates data exchange + * between the API and the server plugins that rely on the API. + */ +public object SharedMemory { + + // Holds shared game-related data + public val games: SharedGameData = SharedGameData() +} + diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/coroutine/exception/SilentCancellationException.kt b/src/main/kotlin/com/github/rushyverse/api/coroutine/exception/SilentCancellationException.kt similarity index 87% rename from src/main/kotlin/io/github/distractic/bukkit/api/coroutine/exception/SilentCancellationException.kt rename to src/main/kotlin/com/github/rushyverse/api/coroutine/exception/SilentCancellationException.kt index 4b234cf2..0875dc5f 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/coroutine/exception/SilentCancellationException.kt +++ b/src/main/kotlin/com/github/rushyverse/api/coroutine/exception/SilentCancellationException.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.coroutine.exception +package com.github.rushyverse.api.coroutine.exception import kotlin.coroutines.cancellation.CancellationException diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/delegate/DelegatePlayer.kt b/src/main/kotlin/com/github/rushyverse/api/delegate/DelegatePlayer.kt similarity index 85% rename from src/main/kotlin/io/github/distractic/bukkit/api/delegate/DelegatePlayer.kt rename to src/main/kotlin/com/github/rushyverse/api/delegate/DelegatePlayer.kt index 27ab3c1b..3585853d 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/delegate/DelegatePlayer.kt +++ b/src/main/kotlin/com/github/rushyverse/api/delegate/DelegatePlayer.kt @@ -1,6 +1,6 @@ -package io.github.distractic.bukkit.api.delegate +package com.github.rushyverse.api.delegate -import io.github.distractic.bukkit.api.koin.inject +import com.github.rushyverse.api.koin.inject import org.bukkit.Server import org.bukkit.entity.Player import java.util.* diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/delegate/DelegateWorld.kt b/src/main/kotlin/com/github/rushyverse/api/delegate/DelegateWorld.kt similarity index 85% rename from src/main/kotlin/io/github/distractic/bukkit/api/delegate/DelegateWorld.kt rename to src/main/kotlin/com/github/rushyverse/api/delegate/DelegateWorld.kt index 45c8af6f..664fc85e 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/delegate/DelegateWorld.kt +++ b/src/main/kotlin/com/github/rushyverse/api/delegate/DelegateWorld.kt @@ -1,6 +1,6 @@ -package io.github.distractic.bukkit.api.delegate +package com.github.rushyverse.api.delegate -import io.github.distractic.bukkit.api.koin.inject +import com.github.rushyverse.api.koin.inject import org.bukkit.Server import org.bukkit.World import java.util.* diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/ConfigurationSectionExt.kt b/src/main/kotlin/com/github/rushyverse/api/extension/ConfigurationSectionExt.kt new file mode 100644 index 00000000..b1a12308 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/extension/ConfigurationSectionExt.kt @@ -0,0 +1,25 @@ +package com.github.rushyverse.api.extension + +import org.bukkit.Material +import org.bukkit.configuration.ConfigurationSection + +private fun ConfigurationSection.sectionException(): Nothing = + throw NullPointerException("ConfigurationSection not found: $currentPath") + +private fun ConfigurationSection.fieldException(): Nothing = + throw NullPointerException("Configuration Field not found: $currentPath") + +public fun ConfigurationSection.getSectionOrException(sectionName: String): ConfigurationSection { + return getConfigurationSection(sectionName) + ?: sectionException() +} + +public fun ConfigurationSection.getIntOrException(fieldPath: String): Int = + getInt(fieldPath) ?: fieldException() + + +public fun ConfigurationSection.getStringOrException(fieldPath: String): String = + getString(fieldPath) ?: fieldException() + +public fun ConfigurationSection.getMaterialOrException(fieldPath: String): Material = + Material.getMaterial(getStringOrException(fieldPath)) ?: fieldException() \ No newline at end of file diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_CommandSender.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_CommandSender.kt similarity index 96% rename from src/main/kotlin/io/github/distractic/bukkit/api/extension/_CommandSender.kt rename to src/main/kotlin/com/github/rushyverse/api/extension/_CommandSender.kt index 084b5cda..8badf3a3 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_CommandSender.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_CommandSender.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import net.kyori.adventure.text.format.NamedTextColor import org.bukkit.command.CommandSender diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Comparable.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Comparable.kt similarity index 87% rename from src/main/kotlin/io/github/distractic/bukkit/api/extension/_Comparable.kt rename to src/main/kotlin/com/github/rushyverse/api/extension/_Comparable.kt index b955072d..53c46c02 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Comparable.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Comparable.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension /** * Get the lowest and highest value in a specific index. diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Component.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Component.kt similarity index 98% rename from src/main/kotlin/io/github/distractic/bukkit/api/extension/_Component.kt rename to src/main/kotlin/com/github/rushyverse/api/extension/_Component.kt index 75ffe09b..47ec9af6 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Component.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Component.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import net.kyori.adventure.text.* import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_CoroutineScope.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt similarity index 96% rename from src/main/kotlin/io/github/distractic/bukkit/api/extension/_CoroutineScope.kt rename to src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt index 0e9bf148..06120773 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_CoroutineScope.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt @@ -1,6 +1,6 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension -import io.github.distractic.bukkit.api.schedule.SchedulerTask +import com.github.rushyverse.api.schedule.SchedulerTask import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.withContext diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Duration.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Duration.kt similarity index 96% rename from src/main/kotlin/io/github/distractic/bukkit/api/extension/_Duration.kt rename to src/main/kotlin/com/github/rushyverse/api/extension/_Duration.kt index 69f68f7a..bb7956d7 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Duration.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Duration.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Event.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Event.kt similarity index 89% rename from src/main/kotlin/io/github/distractic/bukkit/api/extension/_Event.kt rename to src/main/kotlin/com/github/rushyverse/api/extension/_Event.kt index 23493fd6..7acf0c83 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Event.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Event.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import org.bukkit.entity.Damageable import org.bukkit.event.entity.EntityDamageEvent diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_ItemStack.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_ItemStack.kt similarity index 99% rename from src/main/kotlin/io/github/distractic/bukkit/api/extension/_ItemStack.kt rename to src/main/kotlin/com/github/rushyverse/api/extension/_ItemStack.kt index 5e2b2705..cdd172ca 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_ItemStack.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_ItemStack.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import org.bukkit.Material import org.bukkit.inventory.ItemStack diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_JavaPlugin.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_JavaPlugin.kt similarity index 92% rename from src/main/kotlin/io/github/distractic/bukkit/api/extension/_JavaPlugin.kt rename to src/main/kotlin/com/github/rushyverse/api/extension/_JavaPlugin.kt index 2b35e7f6..780c54cf 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_JavaPlugin.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_JavaPlugin.kt @@ -1,7 +1,7 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import com.github.shynixn.mccoroutine.bukkit.registerSuspendingEvents -import io.github.distractic.bukkit.api.item.CraftBuilder +import com.github.rushyverse.api.item.CraftBuilder import org.bukkit.NamespacedKey import org.bukkit.event.Listener import org.bukkit.plugin.java.JavaPlugin @@ -35,4 +35,5 @@ public inline fun JavaPlugin.registerCraft( public inline fun JavaPlugin.registerListener(listenerBuilder: () -> Listener) { contract { callsInPlace(listenerBuilder, InvocationKind.EXACTLY_ONCE) } server.pluginManager.registerSuspendingEvents(listenerBuilder(), this) -} \ No newline at end of file +} + diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Listener.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Listener.kt similarity index 98% rename from src/main/kotlin/io/github/distractic/bukkit/api/extension/_Listener.kt rename to src/main/kotlin/com/github/rushyverse/api/extension/_Listener.kt index cde11e66..da1aa938 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Listener.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Listener.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeoutOrNull diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Location.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Location.kt similarity index 92% rename from src/main/kotlin/io/github/distractic/bukkit/api/extension/_Location.kt rename to src/main/kotlin/com/github/rushyverse/api/extension/_Location.kt index 17c1cb35..181841f4 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Location.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Location.kt @@ -1,5 +1,6 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension +import com.github.rushyverse.api.world.Pos import org.bukkit.Location import org.bukkit.World @@ -85,4 +86,6 @@ public fun Location.copy( z: Double = this.z, yaw: Float = this.yaw, pitch: Float = this.pitch, -): Location = Location(world, x, y, z, yaw, pitch) \ No newline at end of file +): Location = Location(world, x, y, z, yaw, pitch) + +public fun Location.toPos(): Pos = Pos(x, y, z, yaw, pitch) \ No newline at end of file diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_MerchantRecipe.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_MerchantRecipe.kt similarity index 97% rename from src/main/kotlin/io/github/distractic/bukkit/api/extension/_MerchantRecipe.kt rename to src/main/kotlin/com/github/rushyverse/api/extension/_MerchantRecipe.kt index 26f4f19a..39aad712 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_MerchantRecipe.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_MerchantRecipe.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import org.bukkit.inventory.ItemStack import org.bukkit.inventory.MerchantRecipe diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_PersistentDataHolder.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_PersistentDataHolder.kt similarity index 91% rename from src/main/kotlin/io/github/distractic/bukkit/api/extension/_PersistentDataHolder.kt rename to src/main/kotlin/com/github/rushyverse/api/extension/_PersistentDataHolder.kt index 3072dca9..fc400dcb 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_PersistentDataHolder.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_PersistentDataHolder.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import org.bukkit.persistence.PersistentDataContainer import org.bukkit.persistence.PersistentDataHolder diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Player.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Player.kt similarity index 96% rename from src/main/kotlin/io/github/distractic/bukkit/api/extension/_Player.kt rename to src/main/kotlin/com/github/rushyverse/api/extension/_Player.kt index 83af8c7b..c86286b6 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Player.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Player.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import com.destroystokyo.paper.profile.PlayerProfile import org.bukkit.entity.Player diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_PlayerProfile.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_PlayerProfile.kt similarity index 94% rename from src/main/kotlin/io/github/distractic/bukkit/api/extension/_PlayerProfile.kt rename to src/main/kotlin/com/github/rushyverse/api/extension/_PlayerProfile.kt index 5446b8de..8200ae62 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_PlayerProfile.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_PlayerProfile.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import com.destroystokyo.paper.profile.PlayerProfile import com.destroystokyo.paper.profile.ProfileProperty diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Runnable.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Runnable.kt similarity index 96% rename from src/main/kotlin/io/github/distractic/bukkit/api/extension/_Runnable.kt rename to src/main/kotlin/com/github/rushyverse/api/extension/_Runnable.kt index a528e9c9..510b531d 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Runnable.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Runnable.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import com.github.shynixn.mccoroutine.bukkit.asyncDispatcher import com.github.shynixn.mccoroutine.bukkit.minecraftDispatcher diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt new file mode 100644 index 00000000..c6065682 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt @@ -0,0 +1,208 @@ +@file:JvmName("StringUtils") +@file:JvmMultifileClass + +package com.github.rushyverse.api.extension + +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.TextComponent +import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.minimessage.MiniMessage +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver +import org.bukkit.ChatColor +import java.math.BigInteger +import java.util.* + +/** + * Length of the value of UUID. + */ +public const val UUID_SIZE: Int = 36 + +/** + * Apply the coloration of Bukkit + * @see ChatColor.translateAlternateColorCodes + * @receiver String that will be analyzed to create a String colored + * @return A new String with the coloration of Bukkit + */ +public fun String.colored(): String = ChatColor.translateAlternateColorCodes('&', this) + +/*** + * Encodes the specified byte array into a String using the [Base64] encoding scheme. + * @receiver String to encode. + * @return A String containing the resulting Base64 encoded characters. + */ +public fun String.encodeBase64ToString(): String = Base64.getEncoder().encodeToString(this.toByteArray(Charsets.UTF_8)) + +/*** + * Encodes the specified byte array into a String using the [Base64] encoding scheme. + * @receiver Array of byte to encode. + * @return A byte array containing the resulting Base64 encoded characters. + */ +public fun ByteArray.encodeBase64ToString(): String = Base64.getEncoder().encodeToString(this) + +/** + * Decodes a Base64 encoded String into a newly-allocated byte array using the [Base64] encoding scheme. + * @receiver String to decode. + * @return A new String containing the decoded bytes. + */ +public fun String.decodeBase64ToString(): String = Base64.getDecoder().decode(this).decodeToString() + +/** + * Decodes a Base64 encoded String into a newly-allocated byte array using the [Base64] encoding scheme. + * @receiver Array of byte< to decode. + * @return A byte array containing the decoded bytes. + */ +public fun String.decodeBase64Bytes(): ByteArray = Base64.getDecoder().decode(this) + +/** + * Creates a [UUID] from the string standard. + * The string must have the strict format of UUID (with dashes). + * If the string cannot be converted to UUID, returns null. + * @receiver String with UUID format. + * @return The UUID instance equals to the string value or null if the format is not valid. + */ +public fun String.toUUIDStrictOrNull(): UUID? = try { + toUUIDStrict() +} catch (_: Exception) { + null +} + +/** + * Creates a [UUID] from the string standard. + * The string must have the strict format of UUID (with dashes). + * If the string cannot be converted to UUID, throws an exception. + * @receiver String with UUID format. + * @return The UUID instance equals to the string value. + * @throws IllegalArgumentException Exception if the value is not a valid uuid. + */ +@Throws(IllegalArgumentException::class) +public fun String.toUUIDStrict(): UUID = UUID.fromString(this) + +/** + * Creates a [UUID] from a string. + * The string can have dashes or not. + * If the string cannot be converted to UUID, returns null. + * @receiver String with UUID format. + * @return The UUID instance equals to the string value or null if the format is not valid. + */ +public fun String.toUUIDOrNull(): UUID? = try { + toUUID() +} catch (_: Exception) { + null +} + +/** + * Creates a [UUID] from a string. + * The string can have dashes or not. + * If the string cannot be converted to UUID, throws an exception. + * @receiver String with UUID format. + * @return The UUID instance equals to the string value. + * @throws IllegalArgumentException Exception if the value is not a valid uuid. + */ +@Throws(IllegalArgumentException::class) +public fun String.toUUID(): UUID { + val length = this.length + if (length == UUID_SIZE) { + return toUUIDStrict() + } else if (length == UUID_SIZE - 4) { // -4 because of dashes + val idHex = BigInteger(this, 16) + return UUID(idHex.shiftRight(64).toLong(), idHex.toLong()) + } + throw IllegalArgumentException("Invalid UUID format: $this") +} + +/** + * Default max line for a lore line. + * This value is defined by looking with the default Minecraft size application. + */ +public const val DEFAULT_LORE_LINE_LENGTH: Int = 30 + +/** + * Transform a sequence of strings to a component. + * Each string will be transformed into a component and then joined together by a new line. + * @receiver The sequence of strings to transform. + * @param transform The transform function to apply to each string. + * @return A component that contains all the strings. + */ +public inline fun Sequence.toLore( + crossinline transform: TextComponent.Builder.() -> Unit = { + color(NamedTextColor.GRAY) + } +): List { + return map { Component.text().content(it).apply(transform).build() }.toList() +} + +/** + * Transform a collection of strings to a component. + * Each string will be transformed into a component and then joined together by a new line. + * @receiver The collection of strings to transform. + * @param transform A function that will be applied to each component. + * @return A component that contains all the strings. + */ +public inline fun Collection.toLore( + crossinline transform: TextComponent.Builder.() -> Unit = { + color(NamedTextColor.GRAY) + } +): List { + if (isEmpty()) return emptyList() + return map { Component.text().content(it).apply(transform).build() } +} + +/** + * Transform a string into a list of string by cutting it. + * If the string is too large and doesn't have any space, it will be cut each [lineLength] characters and a '-' will be added. + * If the string contains a space, it will be cut at the space. + * @receiver String to transform. + * @param lineLength Max size of each string. + * @return A list with strings with length less or equals to [lineLength]. + */ +public fun String.toFormattedLore(lineLength: Int = DEFAULT_LORE_LINE_LENGTH): List { + return toFormattedLoreSequence(lineLength).toList() +} + +/** + * Transform a string into a sequence of string by cutting. + * If the string is too large and doesn't have any space, it will be cut each [lineLength] characters and a '-' will be added. + * If the string contains a space, it will be cut at the space. + * @receiver String to transform. + * @param lineLength Max size of each string. + * @return A sequence with strings with length less or equals to [lineLength]. + */ +public fun String.toFormattedLoreSequence(lineLength: Int = DEFAULT_LORE_LINE_LENGTH): Sequence { + if (isEmpty()) return emptySequence() + if (length <= lineLength) return sequenceOf(this) + + var index = 0 + return sequence { + while (index < length) { + val nextIndex = index + lineLength + if (nextIndex >= length) { + yield(substring(index)) + break + } + + val substringToNextIndex = substring(index, nextIndex) + val substringBeforeLastSpace = substringToNextIndex.substringBeforeLast(' ') + val nextChar = get(index + substringBeforeLastSpace.length) + + index += if (nextChar.isWhitespace()) { + yield(substringBeforeLastSpace) + // +1 to skip the space + substringBeforeLastSpace.length + 1 + } else { + yield(substringToNextIndex.dropLast(1) + '-') + substringToNextIndex.lastIndex + } + } + } +} + +/** + * Transforms a string into a component using MiniMessage. + * Will set the color according to the tag in the string. + * The [tagResolver] will be used to resolve the custom tags and replace values. + * @receiver The string used to create the component. + * @param tagResolver The tag resolver used to resolve the custom tags. + * @return The component created from the string. + */ +public fun String.asMiniComponent(vararg tagResolver: TagResolver): Component = + MiniMessage.miniMessage().deserialize(this, *tagResolver) \ No newline at end of file diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Villager.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Villager.kt similarity index 96% rename from src/main/kotlin/io/github/distractic/bukkit/api/extension/_Villager.kt rename to src/main/kotlin/com/github/rushyverse/api/extension/_Villager.kt index 6ee979de..ade89841 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_Villager.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Villager.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import org.bukkit.NamespacedKey import org.bukkit.entity.Villager diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_World.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_World.kt similarity index 97% rename from src/main/kotlin/io/github/distractic/bukkit/api/extension/_World.kt rename to src/main/kotlin/com/github/rushyverse/api/extension/_World.kt index 68fa2f05..14579085 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_World.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_World.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import kotlinx.coroutines.future.await import org.bukkit.Chunk diff --git a/src/main/kotlin/com/github/rushyverse/api/game/GameData.kt b/src/main/kotlin/com/github/rushyverse/api/game/GameData.kt new file mode 100644 index 00000000..c8575a83 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/game/GameData.kt @@ -0,0 +1,8 @@ +package com.github.rushyverse.api.game + +public data class GameData( + val type: String, + val id: Int, + var players: Int = 0, + var state: GameState = GameState.WAITING, +) \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/game/GameState.kt b/src/main/kotlin/com/github/rushyverse/api/game/GameState.kt new file mode 100644 index 00000000..94f43f16 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/game/GameState.kt @@ -0,0 +1,10 @@ +package com.github.rushyverse.api.game + +public enum class GameState { + + WAITING, + STARTING, + STARTED, + ENDING + ; +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/game/SharedGameData.kt b/src/main/kotlin/com/github/rushyverse/api/game/SharedGameData.kt new file mode 100644 index 00000000..a46ab13c --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/game/SharedGameData.kt @@ -0,0 +1,56 @@ +package com.github.rushyverse.api.game + +public class SharedGameData { + + public val games: MutableList = mutableListOf() + private val onChange: MutableSet<() -> Unit> = mutableSetOf() + + /** + * Get the total of players into games + */ + public fun players(): Int = games.sumOf { it.players } + + /** + * Get the total of players into a specific type of games + */ + public fun players(gameType: String): Int = games.filter { it.type == gameType }.sumOf { it.players } + + /** + * Get the players of a specific game + */ + public fun players(gameType: String, gameId: Int): Int = + games.firstOrNull { it.type == gameType && it.id == gameId }?.players ?: 0 + + /** + * Get the state of a specific game + */ + public fun state(gameType: String, gameId: Int): GameState = + games.distinctBy { it.type == gameType }.firstOrNull { it.id == gameId }?.state ?: GameState.WAITING + + + /** + * Count all games of a specific type of game + */ + public fun games(gameType: String): Int = games.filter { it.type == gameType }.size + + public fun subscribeOnChange(unit: () -> Unit) { + onChange.add(unit) + } + + public fun callOnChange() { + onChange.forEach { it.invoke() } + } + + public fun saveUpdate(gameData: GameData) { + val index = games.indexOf(gameData) + if (index == -1) { + games.add(gameData) + } else { + games[index] = gameData + } + + println("Shared memory data : $games") + + callOnChange() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/game/stats/KillableStats.kt b/src/main/kotlin/com/github/rushyverse/api/game/stats/KillableStats.kt new file mode 100644 index 00000000..17929d14 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/game/stats/KillableStats.kt @@ -0,0 +1,15 @@ +package com.github.rushyverse.api.game.stats + +public open class KillableStats( + public var kills: Int = 0, + public var deaths: Int = 0, + +) : Stats { + + public override fun calculateScore(): Int { + val score = kills - deaths + if (score < 0) + return 0 + return score + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/game/stats/Stats.kt b/src/main/kotlin/com/github/rushyverse/api/game/stats/Stats.kt new file mode 100644 index 00000000..a9932944 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/game/stats/Stats.kt @@ -0,0 +1,5 @@ +package com.github.rushyverse.api.game.stats + +public interface Stats { + public fun calculateScore(): Int +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/game/stats/WinnableStats.kt b/src/main/kotlin/com/github/rushyverse/api/game/stats/WinnableStats.kt new file mode 100644 index 00000000..c037febd --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/game/stats/WinnableStats.kt @@ -0,0 +1,14 @@ +package com.github.rushyverse.api.game.stats + +public open class WinnableStats( + public var wins: Int = 0, + public var loses: Int = 0 +) : Stats { + + override fun calculateScore(): Int { + val score = wins - loses + if (score < 0) + return 0 + return score + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt b/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt new file mode 100644 index 00000000..5fa05f6b --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt @@ -0,0 +1,51 @@ +package com.github.rushyverse.api.game.team + +import com.github.rushyverse.api.Plugin.Companion.BUNDLE_API +import com.github.rushyverse.api.translation.SupportedLanguage +import com.github.rushyverse.api.translation.TranslationsProvider +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.Component.text +import java.util.* + +public enum class TeamType { + + WHITE, + RED, + BLUE, + GREEN, + YELLOW, + PURPLE, + AQUA, + ORANGE, + BLACK + ; + + public fun name( + translationsProvider: TranslationsProvider, + locale: Locale = SupportedLanguage.ENGLISH.locale + ): String = translationsProvider.translate("team.${name}", locale, BUNDLE_API) + + public fun textName( + translationsProvider: TranslationsProvider, + locale: Locale = SupportedLanguage.ENGLISH.locale + ): Component = text(name(translationsProvider, locale)) + + public fun memberAdjective( + translationsProvider: TranslationsProvider, + locale: Locale = SupportedLanguage.ENGLISH.locale + ): String { + val key = "team.${name}.member" + val result = translationsProvider.translate(key, locale, BUNDLE_API) + + if (result == key) { + return name(translationsProvider, locale) + } + + return result + } + + public fun textMemberAdjective( + translationsProvider: TranslationsProvider, + locale: Locale = SupportedLanguage.ENGLISH.locale + ): Component = text(memberAdjective(translationsProvider, locale)) +} \ No newline at end of file diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/item/CraftBuilder.kt b/src/main/kotlin/com/github/rushyverse/api/item/CraftBuilder.kt similarity index 97% rename from src/main/kotlin/io/github/distractic/bukkit/api/item/CraftBuilder.kt rename to src/main/kotlin/com/github/rushyverse/api/item/CraftBuilder.kt index 7c1b38ff..3287a53e 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/item/CraftBuilder.kt +++ b/src/main/kotlin/com/github/rushyverse/api/item/CraftBuilder.kt @@ -1,7 +1,7 @@ -package io.github.distractic.bukkit.api.item +package com.github.rushyverse.api.item -import io.github.distractic.bukkit.api.extension.item -import io.github.distractic.bukkit.api.item.exception.CraftResultMissingException +import com.github.rushyverse.api.extension.item +import com.github.rushyverse.api.item.exception.CraftResultMissingException import org.bukkit.Material import org.bukkit.NamespacedKey import org.bukkit.inventory.ItemStack diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/item/exception/CraftResultMissingException.kt b/src/main/kotlin/com/github/rushyverse/api/item/exception/CraftResultMissingException.kt similarity index 75% rename from src/main/kotlin/io/github/distractic/bukkit/api/item/exception/CraftResultMissingException.kt rename to src/main/kotlin/com/github/rushyverse/api/item/exception/CraftResultMissingException.kt index b15f2d3d..4d784b3b 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/item/exception/CraftResultMissingException.kt +++ b/src/main/kotlin/com/github/rushyverse/api/item/exception/CraftResultMissingException.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.item.exception +package com.github.rushyverse.api.item.exception /** * Exception when the result item is not defined. diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/koin/CraftContext.kt b/src/main/kotlin/com/github/rushyverse/api/koin/CraftContext.kt similarity index 99% rename from src/main/kotlin/io/github/distractic/bukkit/api/koin/CraftContext.kt rename to src/main/kotlin/com/github/rushyverse/api/koin/CraftContext.kt index ed842dee..be43a734 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/koin/CraftContext.kt +++ b/src/main/kotlin/com/github/rushyverse/api/koin/CraftContext.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.koin +package com.github.rushyverse.api.koin import org.koin.core.Koin import org.koin.core.KoinApplication diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/listener/PlayerListener.kt b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt similarity index 63% rename from src/main/kotlin/io/github/distractic/bukkit/api/listener/PlayerListener.kt rename to src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt index 18ca05e8..367a1094 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/listener/PlayerListener.kt +++ b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt @@ -1,27 +1,32 @@ -package io.github.distractic.bukkit.api.listener - -import io.github.distractic.bukkit.api.Plugin -import io.github.distractic.bukkit.api.coroutine.exception.SilentCancellationException -import io.github.distractic.bukkit.api.koin.inject -import io.github.distractic.bukkit.api.player.Client -import io.github.distractic.bukkit.api.player.ClientManager -import io.github.distractic.bukkit.api.player.exception.ClientAlreadyExistsException +package com.github.rushyverse.api.listener + +import com.github.rushyverse.api.Plugin +import com.github.rushyverse.api.coroutine.exception.SilentCancellationException +import com.github.rushyverse.api.koin.inject +import com.github.rushyverse.api.player.* +import com.github.rushyverse.api.player.exception.ClientAlreadyExistsException import kotlinx.coroutines.cancel +import net.kyori.adventure.text.Component import org.bukkit.entity.Player import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority import org.bukkit.event.Listener import org.bukkit.event.player.PlayerJoinEvent import org.bukkit.event.player.PlayerQuitEvent +import java.util.concurrent.atomic.AtomicReference +import java.util.logging.Logger /** * Main listener to manager instance of clients for the player entering and exiting the server. * @property plugin Java plugin. * @property clients Client manager to store and remove client instances. */ -public class PlayerListener(private val plugin: Plugin) : Listener { +public class PlayerListener( + private val plugin: Plugin +) : Listener { private val clients: ClientManager by inject(plugin.id) + private val logger: Logger by inject(plugin.id) /** * Handle the join event to create and store a new client. @@ -30,8 +35,14 @@ public class PlayerListener(private val plugin: Plugin) : Listener { */ @EventHandler(priority = EventPriority.LOWEST) public suspend fun onJoin(event: PlayerJoinEvent) { + println("Player join $event for plugin $plugin") val player = event.player - createAndSaveClient(player) + val clientCreated = createAndSaveClient(player) + val joinMessage = AtomicReference(event.joinMessage()) + + plugin.clientEvents.onJoin(clientCreated, joinMessage) + + event.joinMessage(joinMessage.get()) } /** @@ -57,6 +68,13 @@ public class PlayerListener(private val plugin: Plugin) : Listener { public suspend fun onQuit(event: PlayerQuitEvent) { val player = event.player val client = clients.removeClient(player) ?: return + val quitMessage = AtomicReference(event.quitMessage()) + + plugin.clientEvents.onQuit(client, quitMessage) + + client.fastBoard.delete() client.cancel(SilentCancellationException("The player ${player.name} (${player.uniqueId}) left")) + + event.quitMessage(quitMessage.get()) } } \ No newline at end of file diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/listener/VillagerListener.kt b/src/main/kotlin/com/github/rushyverse/api/listener/VillagerListener.kt similarity index 87% rename from src/main/kotlin/io/github/distractic/bukkit/api/listener/VillagerListener.kt rename to src/main/kotlin/com/github/rushyverse/api/listener/VillagerListener.kt index 2041c5db..0e23d791 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/listener/VillagerListener.kt +++ b/src/main/kotlin/com/github/rushyverse/api/listener/VillagerListener.kt @@ -1,6 +1,6 @@ -package io.github.distractic.bukkit.api.listener +package com.github.rushyverse.api.listener -import io.github.distractic.bukkit.api.extension.keepProfession +import com.github.rushyverse.api.extension.keepProfession import org.bukkit.event.EventHandler import org.bukkit.event.Listener import org.bukkit.event.entity.VillagerCareerChangeEvent diff --git a/src/main/kotlin/com/github/rushyverse/api/player/Client.kt b/src/main/kotlin/com/github/rushyverse/api/player/Client.kt new file mode 100644 index 00000000..417194b0 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/player/Client.kt @@ -0,0 +1,44 @@ +package com.github.rushyverse.api.player + +import fr.mrmicky.fastboard.FastBoard +import com.github.rushyverse.api.delegate.DelegatePlayer +import com.github.rushyverse.api.player.exception.PlayerNotFoundException +import com.github.rushyverse.api.translation.SupportedLanguage +import kotlinx.coroutines.CoroutineScope +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.Component.text +import org.bukkit.entity.Player +import java.util.* + +/** + * Client to store and manage data about player.* + * @property playerUUID Player's uuid. + * @property player Player linked to the client. + */ +public open class Client( + pluginId: String, + public val playerUUID: UUID, + coroutineScope: CoroutineScope +) : CoroutineScope by coroutineScope { + + public val player: Player? by DelegatePlayer(pluginId, playerUUID) + + public val fastBoard: FastBoard by lazy { FastBoard(player) } + + public var lang: SupportedLanguage = SupportedLanguage.ENGLISH + + public val locale: Locale get() = lang.locale + + /** + * Retrieve the instance of player. + * If the player is not found from the server, thrown an exception. + * @return The instance of player. + */ + public fun requirePlayer(): Player = + player ?: throw PlayerNotFoundException("The player cannot be retrieved from the server") + + public fun send(text: Component): Unit = requirePlayer().sendMessage(text) + + public fun send(message: String): Unit = send(text(message)) + +} \ No newline at end of file diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManager.kt b/src/main/kotlin/com/github/rushyverse/api/player/ClientManager.kt similarity index 98% rename from src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManager.kt rename to src/main/kotlin/com/github/rushyverse/api/player/ClientManager.kt index e85cd350..ef8bfb33 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManager.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/ClientManager.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.player +package com.github.rushyverse.api.player import org.bukkit.entity.Player diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImpl.kt b/src/main/kotlin/com/github/rushyverse/api/player/ClientManagerImpl.kt similarity index 94% rename from src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImpl.kt rename to src/main/kotlin/com/github/rushyverse/api/player/ClientManagerImpl.kt index a6a93968..f2e44c7b 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImpl.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/ClientManagerImpl.kt @@ -1,6 +1,6 @@ -package io.github.distractic.bukkit.api.player +package com.github.rushyverse.api.player -import io.github.distractic.bukkit.api.player.exception.ClientNotFoundException +import com.github.rushyverse.api.player.exception.ClientNotFoundException import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.bukkit.entity.Player diff --git a/src/main/kotlin/com/github/rushyverse/api/player/PluginClientEvents.kt b/src/main/kotlin/com/github/rushyverse/api/player/PluginClientEvents.kt new file mode 100644 index 00000000..f08577dc --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/player/PluginClientEvents.kt @@ -0,0 +1,31 @@ +package com.github.rushyverse.api.player + +import net.kyori.adventure.text.Component +import java.util.concurrent.atomic.AtomicReference + +/** + * Abstract class for handling client events individually for API dependant plugins. + * This approach is necessary to handle client events individually + * for each plugin depending on the API. + * + * The call event approach is therefore not favorable because + * it broadcasts the event for all dependent plugins currently running. + */ +public abstract class PluginClientEvents { + + /** + * Manage when the plugin creates a new client. + * @param client The ready-to-use client. + * @param joinMessage The message to display when creating this client, + * it is directly affiliated to the PlayerJoinEvent. + */ + public abstract suspend fun onJoin(client: Client, joinMessage: AtomicReference) + + /** + * Manage when the plugin removes a client. + * @param client The client being deleted. + * @param quitMessage The message to display when deleting this client, + * it is directly affiliated to the PlayerQuitEvent. + */ + public abstract suspend fun onQuit(client: Client, quitMessage: AtomicReference) +} \ No newline at end of file diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/player/exception/ClientAlreadyExistsException.kt b/src/main/kotlin/com/github/rushyverse/api/player/exception/ClientAlreadyExistsException.kt similarity index 73% rename from src/main/kotlin/io/github/distractic/bukkit/api/player/exception/ClientAlreadyExistsException.kt rename to src/main/kotlin/com/github/rushyverse/api/player/exception/ClientAlreadyExistsException.kt index 59810b3d..ea5eba74 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/player/exception/ClientAlreadyExistsException.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/exception/ClientAlreadyExistsException.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.player.exception +package com.github.rushyverse.api.player.exception /** * Exception if a client is already exists for a player. diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/player/exception/ClientNotFoundException.kt b/src/main/kotlin/com/github/rushyverse/api/player/exception/ClientNotFoundException.kt similarity index 71% rename from src/main/kotlin/io/github/distractic/bukkit/api/player/exception/ClientNotFoundException.kt rename to src/main/kotlin/com/github/rushyverse/api/player/exception/ClientNotFoundException.kt index 43e4d830..b52516b6 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/player/exception/ClientNotFoundException.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/exception/ClientNotFoundException.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.player.exception +package com.github.rushyverse.api.player.exception /** * Exception if a client cannot be found. diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/player/exception/PlayerNotFoundException.kt b/src/main/kotlin/com/github/rushyverse/api/player/exception/PlayerNotFoundException.kt similarity index 72% rename from src/main/kotlin/io/github/distractic/bukkit/api/player/exception/PlayerNotFoundException.kt rename to src/main/kotlin/com/github/rushyverse/api/player/exception/PlayerNotFoundException.kt index 92d574b6..7b94edd4 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/player/exception/PlayerNotFoundException.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/exception/PlayerNotFoundException.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.player.exception +package com.github.rushyverse.api.player.exception /** * Exception if a player is not into the server. diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/player/exception/PlayerQuitException.kt b/src/main/kotlin/com/github/rushyverse/api/player/exception/PlayerQuitException.kt similarity index 82% rename from src/main/kotlin/io/github/distractic/bukkit/api/player/exception/PlayerQuitException.kt rename to src/main/kotlin/com/github/rushyverse/api/player/exception/PlayerQuitException.kt index c3979e58..92a6774d 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/player/exception/PlayerQuitException.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/exception/PlayerQuitException.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.player.exception +package com.github.rushyverse.api.player.exception import kotlin.coroutines.cancellation.CancellationException diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/schedule/AbstractScheduler.kt b/src/main/kotlin/com/github/rushyverse/api/schedule/AbstractScheduler.kt similarity index 95% rename from src/main/kotlin/io/github/distractic/bukkit/api/schedule/AbstractScheduler.kt rename to src/main/kotlin/com/github/rushyverse/api/schedule/AbstractScheduler.kt index 59941ee0..cda3a281 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/schedule/AbstractScheduler.kt +++ b/src/main/kotlin/com/github/rushyverse/api/schedule/AbstractScheduler.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.schedule +package com.github.rushyverse.api.schedule import kotlinx.coroutines.* diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/schedule/Scheduler.kt b/src/main/kotlin/com/github/rushyverse/api/schedule/Scheduler.kt similarity index 92% rename from src/main/kotlin/io/github/distractic/bukkit/api/schedule/Scheduler.kt rename to src/main/kotlin/com/github/rushyverse/api/schedule/Scheduler.kt index 10f71b3c..a2982ee9 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/schedule/Scheduler.kt +++ b/src/main/kotlin/com/github/rushyverse/api/schedule/Scheduler.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.schedule +package com.github.rushyverse.api.schedule import kotlinx.coroutines.CancellationException diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTask.kt b/src/main/kotlin/com/github/rushyverse/api/schedule/SchedulerTask.kt similarity index 99% rename from src/main/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTask.kt rename to src/main/kotlin/com/github/rushyverse/api/schedule/SchedulerTask.kt index 544d40cd..2b75cf4c 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTask.kt +++ b/src/main/kotlin/com/github/rushyverse/api/schedule/SchedulerTask.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.schedule +package com.github.rushyverse.api.schedule import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.coroutineScope diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleNotRegisteredException.kt b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleNotRegisteredException.kt new file mode 100644 index 00000000..5fc5442a --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleNotRegisteredException.kt @@ -0,0 +1,11 @@ +package com.github.rushyverse.api.translation + +import java.util.* + +/** + * Exception used when the system try to use a resource bundle not registered. + * @param bundleName Name of the bundle. + * @param locale Locale searched. + */ +public class ResourceBundleNotRegisteredException(public val bundleName: String, public val locale: Locale) : + RuntimeException("The bundle [$bundleName] for locale [$locale] is not registered.") \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationsProvider.kt b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationsProvider.kt new file mode 100644 index 00000000..ac32d59e --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationsProvider.kt @@ -0,0 +1,85 @@ +package com.github.rushyverse.api.translation + +import mu.KotlinLogging +import java.text.MessageFormat +import java.util.* + +/** + * Loads the [ResourceBundle] called [bundleName] for all supported locales from [SupportedLanguage]. + * @see ResourceBundleTranslationsProvider.registerResourceBundle + */ +public fun ResourceBundleTranslationsProvider.registerResourceBundleForSupportedLocales( + bundleName: String, + loader: (String, Locale) -> ResourceBundle +) { + SupportedLanguage.values().forEach { + registerResourceBundle(bundleName, it.locale, loader) + } +} + +private val logger = KotlinLogging.logger { } + +/** + * Translation provider backed by Java's [ResourceBundle]s. This makes use of `.properties` files that are standard + * across the Java ecosystem. + */ +public open class ResourceBundleTranslationsProvider : TranslationsProvider() { + + private val bundles: MutableMap, ResourceBundle> = mutableMapOf() + + override fun get(key: String, locale: Locale, bundleName: String): String { + return getBundle(locale, bundleName).getString(key) + } + + override fun translate( + key: String, + locale: Locale, + bundleName: String, + replacements: Array + ): String { + val string = try { + get(key, locale, bundleName) + } catch (e: MissingResourceException) { + logger.error("Unable to find translation for key '$key' in bundles: '$bundleName'") + return key + } + + return MessageFormat(string, locale).format(replacements) + } + + /** + * Retrieve the registered bundle or load it. + * @param locale Locale. + * @param bundleName Name of the bundle. + * @return The loaded instance of [ResourceBundle]. + */ + protected open fun getBundle(locale: Locale, bundleName: String): ResourceBundle { + return bundles[createKey(bundleName, locale)] + ?: throw ResourceBundleNotRegisteredException(bundleName, locale) + } + + /** + * Loads the [ResourceBundle] called [bundleName] for [locale] and register it in [bundles] + * @param bundleName Name of the bundle. + * @param locale Locale. + * @return The loaded instance of [ResourceBundle]. + */ + public fun registerResourceBundle( + bundleName: String, + locale: Locale, + loader: (String, Locale) -> ResourceBundle + ): ResourceBundle { + logger.info("Getting bundle $bundleName for locale $locale") + return loader(bundleName, locale).apply { + bundles[createKey(bundleName, locale)] = this + } + } + + /** + * Create the key to retrieve bundle. + * @param bundleName Name of the bundle. + * @param locale Locale. + * @return The key created. + */ + private fun createKey(bundleName: String, locale: Locale) = bundleName to locale +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt b/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt new file mode 100644 index 00000000..ce5d52e2 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt @@ -0,0 +1,13 @@ +package com.github.rushyverse.api.translation + +import java.util.* + +/** + * List of supported locales to translate keys. + */ +public enum class SupportedLanguage(public val displayName: String, public val locale: Locale) { + + ENGLISH("English", Locale("en", "gb")), + FRENCH("Français", Locale("fr", "fr")), + SPANISH("Español", Locale("es", "es")) +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/TranslationsProvider.kt b/src/main/kotlin/com/github/rushyverse/api/translation/TranslationsProvider.kt new file mode 100644 index 00000000..82c81bb9 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/translation/TranslationsProvider.kt @@ -0,0 +1,43 @@ +package com.github.rushyverse.api.translation + +import java.util.* + +/** + * Translation provider interface, in charge of taking string keys and returning translated strings. + */ +public abstract class TranslationsProvider { + + /** + * Get a translation by key from the given locale and bundle name. + */ + public abstract fun get(key: String, locale: Locale, bundleName: String): String + + /** + * Get a formatted translation using the provided arguments. + */ + public abstract fun translate( + key: String, + locale: Locale, + bundleName: String, + replacements: Array + ): String + + /** + * Get a formatted translation using the provided arguments. + */ + public fun translate( + key: String, + locale: Locale, + bundleName: String + ): String = translate(key, locale, bundleName, emptyArray()) + + /** + * Get a formatted translation using the provided arguments. + */ + public fun translate( + key: String, + locale: Locale, + bundleName: String, + replacements: Collection + ): String = translate(key, locale, bundleName, replacements.toTypedArray()) +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/world/Cuboid.kt b/src/main/kotlin/com/github/rushyverse/api/world/Cuboid.kt new file mode 100644 index 00000000..004c4343 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/world/Cuboid.kt @@ -0,0 +1,39 @@ +package com.github.rushyverse.api.world + +import com.github.rushyverse.api.extension.getStringOrException +import org.bukkit.configuration.ConfigurationSection +import kotlin.jvm.internal.Intrinsics + +public class Cuboid( + public val pos1: Pos, + public val pos2: Pos +) { + + public operator fun contains(position: Pos): Boolean { + Intrinsics.checkNotNullParameter(position, "position") + val minX: Double = pos1.x.coerceAtMost(pos2.x) + val maxX: Double = pos1.x.coerceAtLeast(pos2.x) + val minY: Double = pos1.y.coerceAtMost(pos2.y) + val maxY: Double = pos1.y.coerceAtLeast(pos2.y) + val minZ: Double = pos1.z.coerceAtMost(pos2.z) + val maxZ: Double = pos1.z.coerceAtLeast(pos2.z) + val x: Double = position.x + if (x in minX..maxX) { + val y: Double = position.y + if (y in minY..maxY) { + val z: Double = position.z + if (z in minZ..maxZ) { + return true + } + } + } + return false + } + + public companion object { + public fun parse(section: ConfigurationSection): Cuboid = Cuboid( + Pos.parse(section.getStringOrException("pos1")), + Pos.parse(section.getStringOrException("pos2")) + ) + } +} diff --git a/src/main/kotlin/com/github/rushyverse/api/world/Pos.kt b/src/main/kotlin/com/github/rushyverse/api/world/Pos.kt new file mode 100644 index 00000000..9d2fa9bd --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/world/Pos.kt @@ -0,0 +1,36 @@ +package com.github.rushyverse.api.world + +import org.bukkit.Location +import org.bukkit.World + +public data class Pos( + val x: Double, + val y: Double, + val z: Double, + val yaw: Float = 0F, + val pitch: Float = 0F +) { + public companion object { + public fun parse(strPosition: String): Pos { + val split = strPosition.split(" ") + + val yaw = if (split.size > 3) { + split[3].toFloat() + } else 0F + + val pitch = if (split.size > 4) { + split[4].toFloat() + } else 0F + + return Pos( + split[0].toDouble(), + split[1].toDouble(), + split[2].toDouble(), + yaw, + pitch, + ) + } + } + + public fun toLocation(world: World): Location = Location(world, x, y, z, yaw, pitch) +} \ No newline at end of file diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/world/exception/WorldDifferentException.kt b/src/main/kotlin/com/github/rushyverse/api/world/exception/WorldDifferentException.kt similarity index 81% rename from src/main/kotlin/io/github/distractic/bukkit/api/world/exception/WorldDifferentException.kt rename to src/main/kotlin/com/github/rushyverse/api/world/exception/WorldDifferentException.kt index fe54cb81..ade4c9fa 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/world/exception/WorldDifferentException.kt +++ b/src/main/kotlin/com/github/rushyverse/api/world/exception/WorldDifferentException.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.world.exception +package com.github.rushyverse.api.world.exception import org.bukkit.World diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/world/exception/WorldNotFoundException.kt b/src/main/kotlin/com/github/rushyverse/api/world/exception/WorldNotFoundException.kt similarity index 72% rename from src/main/kotlin/io/github/distractic/bukkit/api/world/exception/WorldNotFoundException.kt rename to src/main/kotlin/com/github/rushyverse/api/world/exception/WorldNotFoundException.kt index 57c626c6..69cc4e49 100644 --- a/src/main/kotlin/io/github/distractic/bukkit/api/world/exception/WorldNotFoundException.kt +++ b/src/main/kotlin/com/github/rushyverse/api/world/exception/WorldNotFoundException.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.world.exception +package com.github.rushyverse.api.world.exception /** * Exception if a world is not into the server. diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_String.kt b/src/main/kotlin/io/github/distractic/bukkit/api/extension/_String.kt deleted file mode 100644 index 50ab4a15..00000000 --- a/src/main/kotlin/io/github/distractic/bukkit/api/extension/_String.kt +++ /dev/null @@ -1,106 +0,0 @@ -@file:JvmName("StringUtils") -@file:JvmMultifileClass - -package io.github.distractic.bukkit.api.extension - -import org.bukkit.ChatColor -import java.math.BigInteger -import java.util.* - -/** - * Length of the value of UUID. - */ -public const val UUID_SIZE: Int = 36 - -/** - * Apply the coloration of Bukkit - * @see ChatColor.translateAlternateColorCodes - * @receiver String that will be analyzed to create a String colored - * @return A new String with the coloration of Bukkit - */ -public fun String.colored(): String = ChatColor.translateAlternateColorCodes('&', this) - -/*** - * Encodes the specified byte array into a String using the [Base64] encoding scheme. - * @receiver String to encode. - * @return A String containing the resulting Base64 encoded characters. - */ -public fun String.encodeBase64ToString(): String = Base64.getEncoder().encodeToString(this.toByteArray(Charsets.UTF_8)) - -/*** - * Encodes the specified byte array into a String using the [Base64] encoding scheme. - * @receiver Array of byte to encode. - * @return A byte array containing the resulting Base64 encoded characters. - */ -public fun ByteArray.encodeBase64ToString(): String = Base64.getEncoder().encodeToString(this) - -/** - * Decodes a Base64 encoded String into a newly-allocated byte array using the [Base64] encoding scheme. - * @receiver String to decode. - * @return A new String containing the decoded bytes. - */ -public fun String.decodeBase64ToString(): String = Base64.getDecoder().decode(this).decodeToString() - -/** - * Decodes a Base64 encoded String into a newly-allocated byte array using the [Base64] encoding scheme. - * @receiver Array of byte< to decode. - * @return A byte array containing the decoded bytes. - */ -public fun String.decodeBase64Bytes(): ByteArray = Base64.getDecoder().decode(this) - -/** - * Creates a [UUID] from the string standard. - * The string must have the strict format of UUID (with dashes). - * If the string cannot be converted to UUID, returns null. - * @receiver String with UUID format. - * @return The UUID instance equals to the string value or null if the format is not valid. - */ -public fun String.toUUIDStrictOrNull(): UUID? = try { - toUUIDStrict() -} catch (_: Exception) { - null -} - -/** - * Creates a [UUID] from the string standard. - * The string must have the strict format of UUID (with dashes). - * If the string cannot be converted to UUID, throws an exception. - * @receiver String with UUID format. - * @return The UUID instance equals to the string value. - * @throws IllegalArgumentException Exception if the value is not a valid uuid. - */ -@Throws(IllegalArgumentException::class) -public fun String.toUUIDStrict(): UUID = UUID.fromString(this) - -/** - * Creates a [UUID] from a string. - * The string can have dashes or not. - * If the string cannot be converted to UUID, returns null. - * @receiver String with UUID format. - * @return The UUID instance equals to the string value or null if the format is not valid. - */ -public fun String.toUUIDOrNull(): UUID? = try { - toUUID() -} catch (_: Exception) { - null -} - -/** - * Creates a [UUID] from a string. - * The string can have dashes or not. - * If the string cannot be converted to UUID, throws an exception. - * @receiver String with UUID format. - * @return The UUID instance equals to the string value. - * @throws IllegalArgumentException Exception if the value is not a valid uuid. - */ -@Throws(IllegalArgumentException::class) -public fun String.toUUID(): UUID { - val length = this.length - if (length == UUID_SIZE) { - return toUUIDStrict() - } else if (length == UUID_SIZE - 4) { // -4 because of dashes - val idHex = BigInteger(this, 16) - return UUID(idHex.shiftRight(64).toLong(), idHex.toLong()) - } - throw IllegalArgumentException("Invalid UUID format: $this") -} \ No newline at end of file diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/player/Client.kt b/src/main/kotlin/io/github/distractic/bukkit/api/player/Client.kt deleted file mode 100644 index 9d6f233e..00000000 --- a/src/main/kotlin/io/github/distractic/bukkit/api/player/Client.kt +++ /dev/null @@ -1,26 +0,0 @@ -package io.github.distractic.bukkit.api.player - -import io.github.distractic.bukkit.api.delegate.DelegatePlayer -import io.github.distractic.bukkit.api.player.exception.PlayerNotFoundException -import kotlinx.coroutines.CoroutineScope -import org.bukkit.entity.Player -import java.util.* - -/** - * Client to store and manage data about player.* - * @property playerUUID Player's uuid. - * @property player Player linked to the client. - */ -public open class Client(pluginId: String, public val playerUUID: UUID, coroutineScope: CoroutineScope) : - CoroutineScope by coroutineScope { - - public val player: Player? by DelegatePlayer(pluginId, playerUUID) - - /** - * Retrieve the instance of player. - * If the player is not found from the server, thrown an exception. - * @return The instance of player. - */ - public fun requirePlayer(): Player = - player ?: throw PlayerNotFoundException("The player cannot be retrieved from the server") -} \ No newline at end of file diff --git a/src/main/kotlin/io/github/distractic/bukkit/api/world/Cuboid.kt b/src/main/kotlin/io/github/distractic/bukkit/api/world/Cuboid.kt deleted file mode 100644 index 2bda6a2e..00000000 --- a/src/main/kotlin/io/github/distractic/bukkit/api/world/Cuboid.kt +++ /dev/null @@ -1,103 +0,0 @@ -package io.github.distractic.bukkit.api.world - -import io.github.distractic.bukkit.api.extension.minMax -import io.github.distractic.bukkit.api.world.exception.WorldDifferentException -import org.bukkit.Bukkit -import org.bukkit.Location -import org.bukkit.World -import org.bukkit.block.Block - -/** - * Represents an area in a minecraft world. - * @property startLocation Position with minimal coordinate's value. - * @property endLocation Position with maximal coordinate's value. - * @property world World where the cuboid is applied. - */ -public class Cuboid(location1: Location, location2: Location) { - - public companion object { - - /** - * Generate cuboid from a string. - * Each argument is split by space and correspond to a specific data. - * The first argument is the world name. - * The second argument is the x coordinate. - * The third argument is the y coordinate. - * The fourth argument is the z coordinate. - * The fifth argument is the x size. - * The sixth argument is the y size. - * The seventh argument is the z size. - */ - public fun fromString(string: String): Cuboid { - val args = string.split(' ') - val world = Bukkit.getWorld(args[0]) ?: throw IllegalArgumentException("World not found") - val x = args[1].toDouble() - val y = args[2].toDouble() - val z = args[3].toDouble() - val xSize = args[4].toDouble() - val ySize = args[5].toDouble() - val zSize = args[6].toDouble() - return Cuboid(Location(world, x, y, z), Location(world, xSize, ySize, zSize)) - } - } - - public val startLocation: Location - public val endLocation: Location - - public var world: World? - get() = startLocation.world - set(value) { - startLocation.world = value - endLocation.world = value - } - - init { - val world1 = location1.world - val world2 = location2.world - if (world1 != world2) { - throw WorldDifferentException(world1, world2, "The locations don't have the same world") - } - - val (minX, maxX) = minMax(location1.x, location2.x) - val (minY, maxY) = minMax(location1.y, location2.y) - val (minZ, maxZ) = minMax(location1.z, location2.z) - startLocation = Location(world1, minX, minY, minZ) - endLocation = Location(world1, maxX, maxY, maxZ) - } - - /** - * Know if a location is in the area. - * - * @param location Location. - * @return `true` if the location is between bounds, `false` otherwise. - */ - public operator fun contains(location: Location): Boolean = location.world == startLocation.world - && location.x in startLocation.x..endLocation.x - && location.y in startLocation.y..endLocation.y - && location.z in startLocation.z..endLocation.z - - /** - * Create an iterator to iterate on all locations present in the area. - * @return Iterator of location. - */ - public fun locationSequence(): Sequence = sequence { - val world = startLocation.world - for (x in startLocation.blockX..endLocation.blockX) { - val xDouble = x.toDouble() - for (y in startLocation.blockY..endLocation.blockY) { - val yDouble = y.toDouble() - for (z in startLocation.blockZ..endLocation.blockZ) { - yield(Location(world, xDouble, yDouble, z.toDouble())) - } - } - } - } - - /** - * Create an iterator to iterate on all blocks present in the area. - * @return Iterator of location. - */ - public fun blockSequence(): Sequence { - return locationSequence().map { it.world.getBlockAt(it) } - } -} \ No newline at end of file diff --git a/src/main/resources/api_translate.properties b/src/main/resources/api_translate.properties new file mode 100644 index 00000000..8cf5a7c9 --- /dev/null +++ b/src/main/resources/api_translate.properties @@ -0,0 +1,10 @@ +team.white=White +team.red=Red +team.blue=Blue +team.green=Green +team.yellow=Yellow +team.purple=Purple +team.aqua=Aqua +team.black=Black + +player.join.team={0} joined {1} team diff --git a/src/main/resources/api_translate_en_GB.properties b/src/main/resources/api_translate_en_GB.properties new file mode 100644 index 00000000..e69de29b diff --git a/src/main/resources/api_translate_es_ES.properties b/src/main/resources/api_translate_es_ES.properties new file mode 100644 index 00000000..e69de29b diff --git a/src/main/resources/api_translate_fr_FR.properties b/src/main/resources/api_translate_fr_FR.properties new file mode 100644 index 00000000..341b85a8 --- /dev/null +++ b/src/main/resources/api_translate_fr_FR.properties @@ -0,0 +1,14 @@ +team.white=Blanche +team.white.member=Blanc +team.red=Rouge +team.blue=Bleue +team.blue.member=Bleu +team.green=Verte +team.green.member=Vert +team.yellow=Jaune +team.purple=Violette +team.purple.member=Violet +team.aqua=Aqua +team.black=Noire + +player.join.team={0} a rejoint l'quipe {1} \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index c50d1e8e..0e135c1e 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,5 +1,7 @@ -name: bukkit-api +name: paper-api version: 1.0 author: Distractic api-version: 1.19 -main: io.github.distractic.bukkit.api.APIPlugin \ No newline at end of file +main: com.github.rushyverse.api.APIPlugin +depend: + - CommandAPI \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/AbstractKoinTest.kt b/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt similarity index 81% rename from src/test/kotlin/io/github/distractic/bukkit/api/AbstractKoinTest.kt rename to src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt index d0974bd6..e11100fa 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/AbstractKoinTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt @@ -1,8 +1,8 @@ -package io.github.distractic.bukkit.api +package com.github.rushyverse.api -import io.github.distractic.bukkit.api.koin.CraftContext -import io.github.distractic.bukkit.api.koin.loadModule -import io.github.distractic.bukkit.api.utils.getRandomString +import com.github.rushyverse.api.koin.CraftContext +import com.github.rushyverse.api.koin.loadModule +import com.github.rushyverse.api.utils.getRandomString import io.mockk.every import io.mockk.mockk import org.bukkit.Server @@ -13,7 +13,7 @@ import kotlin.test.BeforeTest abstract class AbstractKoinTest { - lateinit var plugin: Plugin + lateinit var plugin: com.github.rushyverse.api.Plugin lateinit var server: Server @@ -47,5 +47,4 @@ abstract class AbstractKoinTest { fun loadTestModule(moduleDeclaration: ModuleDeclaration): Module = loadModule(pluginId, false, moduleDeclaration) - } \ No newline at end of file diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/delegate/DelegateWorldTest.kt b/src/test/kotlin/com/github/rushyverse/api/delegate/DelegateWorldTest.kt similarity index 89% rename from src/test/kotlin/io/github/distractic/bukkit/api/delegate/DelegateWorldTest.kt rename to src/test/kotlin/com/github/rushyverse/api/delegate/DelegateWorldTest.kt index 8bd84b5d..b489457d 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/delegate/DelegateWorldTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/delegate/DelegateWorldTest.kt @@ -1,6 +1,6 @@ -package io.github.distractic.bukkit.api.delegate +package com.github.rushyverse.api.delegate -import io.github.distractic.bukkit.api.AbstractKoinTest +import com.github.rushyverse.api.AbstractKoinTest import io.mockk.every import io.mockk.mockk import org.bukkit.World diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/delegate/PlayerWorldTest.kt b/src/test/kotlin/com/github/rushyverse/api/delegate/PlayerWorldTest.kt similarity index 89% rename from src/test/kotlin/io/github/distractic/bukkit/api/delegate/PlayerWorldTest.kt rename to src/test/kotlin/com/github/rushyverse/api/delegate/PlayerWorldTest.kt index 52824427..d2a4ffd7 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/delegate/PlayerWorldTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/delegate/PlayerWorldTest.kt @@ -1,6 +1,6 @@ -package io.github.distractic.bukkit.api.delegate +package com.github.rushyverse.api.delegate -import io.github.distractic.bukkit.api.AbstractKoinTest +import com.github.rushyverse.api.AbstractKoinTest import io.mockk.every import io.mockk.mockk import org.bukkit.entity.Player diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/CommandSenderExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/CommandSenderExtTest.kt similarity index 89% rename from src/test/kotlin/io/github/distractic/bukkit/api/extension/CommandSenderExtTest.kt rename to src/test/kotlin/com/github/rushyverse/api/extension/CommandSenderExtTest.kt index e3922ad8..c177ffa2 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/extension/CommandSenderExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/CommandSenderExtTest.kt @@ -1,6 +1,6 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension -import io.github.distractic.bukkit.api.utils.getRandomString +import com.github.rushyverse.api.utils.getRandomString import io.mockk.justRun import io.mockk.mockk import io.mockk.slot diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/ComparableExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/ComparableExtTest.kt similarity index 96% rename from src/test/kotlin/io/github/distractic/bukkit/api/extension/ComparableExtTest.kt rename to src/test/kotlin/com/github/rushyverse/api/extension/ComparableExtTest.kt index ce8a05b0..7dff3184 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/extension/ComparableExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/ComparableExtTest.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/CoroutineScopeExt.kt b/src/test/kotlin/com/github/rushyverse/api/extension/CoroutineScopeExt.kt similarity index 93% rename from src/test/kotlin/io/github/distractic/bukkit/api/extension/CoroutineScopeExt.kt rename to src/test/kotlin/com/github/rushyverse/api/extension/CoroutineScopeExt.kt index 5ea82471..969c0f50 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/extension/CoroutineScopeExt.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/CoroutineScopeExt.kt @@ -1,6 +1,6 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension -import io.github.distractic.bukkit.api.schedule.SchedulerTask +import com.github.rushyverse.api.schedule.SchedulerTask import kotlinx.coroutines.* import kotlin.test.Test import kotlin.test.assertEquals diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/DurationExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/DurationExtTest.kt similarity index 98% rename from src/test/kotlin/io/github/distractic/bukkit/api/extension/DurationExtTest.kt rename to src/test/kotlin/com/github/rushyverse/api/extension/DurationExtTest.kt index 10904bba..358edb72 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/extension/DurationExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/DurationExtTest.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/EventExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/EventExtTest.kt similarity index 96% rename from src/test/kotlin/io/github/distractic/bukkit/api/extension/EventExtTest.kt rename to src/test/kotlin/com/github/rushyverse/api/extension/EventExtTest.kt index e7113573..027f341d 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/extension/EventExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/EventExtTest.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import io.mockk.every import io.mockk.mockk diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/ItemStackExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/ItemStackExtTest.kt similarity index 99% rename from src/test/kotlin/io/github/distractic/bukkit/api/extension/ItemStackExtTest.kt rename to src/test/kotlin/com/github/rushyverse/api/extension/ItemStackExtTest.kt index df1b7224..544bfd63 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/extension/ItemStackExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/ItemStackExtTest.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import io.github.distractic.bukkit.api.utils.getRandomString import io.mockk.every diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/JavaPluginExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/JavaPluginExtTest.kt similarity index 99% rename from src/test/kotlin/io/github/distractic/bukkit/api/extension/JavaPluginExtTest.kt rename to src/test/kotlin/com/github/rushyverse/api/extension/JavaPluginExtTest.kt index a6e85b97..534d26e2 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/extension/JavaPluginExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/JavaPluginExtTest.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import io.github.distractic.bukkit.api.item.CraftSlot import io.github.distractic.bukkit.api.item.exception.CraftResultMissingException diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/LocationExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/LocationExtTest.kt similarity index 98% rename from src/test/kotlin/io/github/distractic/bukkit/api/extension/LocationExtTest.kt rename to src/test/kotlin/com/github/rushyverse/api/extension/LocationExtTest.kt index 53eb6e6e..095a8e08 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/extension/LocationExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/LocationExtTest.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import io.github.distractic.bukkit.api.extension.* import io.github.distractic.bukkit.api.utils.getRandomString diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/MerchantRecipeExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/MerchantRecipeExtTest.kt similarity index 98% rename from src/test/kotlin/io/github/distractic/bukkit/api/extension/MerchantRecipeExtTest.kt rename to src/test/kotlin/com/github/rushyverse/api/extension/MerchantRecipeExtTest.kt index 0d8c3bc8..75829ecd 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/extension/MerchantRecipeExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/MerchantRecipeExtTest.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import io.github.distractic.bukkit.api.utils.getRandomString import io.mockk.mockk diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/PersistentDataHolderTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/PersistentDataHolderTest.kt similarity index 92% rename from src/test/kotlin/io/github/distractic/bukkit/api/extension/PersistentDataHolderTest.kt rename to src/test/kotlin/com/github/rushyverse/api/extension/PersistentDataHolderTest.kt index 643c693d..e814076a 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/extension/PersistentDataHolderTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/PersistentDataHolderTest.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import io.github.distractic.bukkit.api.utils.getRandomString import io.mockk.mockk diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/PlayerExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/PlayerExtTest.kt similarity index 98% rename from src/test/kotlin/io/github/distractic/bukkit/api/extension/PlayerExtTest.kt rename to src/test/kotlin/com/github/rushyverse/api/extension/PlayerExtTest.kt index 3ab28664..90c6a393 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/extension/PlayerExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/PlayerExtTest.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import com.destroystokyo.paper.profile.PlayerProfile import io.github.distractic.bukkit.api.utils.getRandomString diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/RunnableExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/RunnableExtTest.kt similarity index 85% rename from src/test/kotlin/io/github/distractic/bukkit/api/extension/RunnableExtTest.kt rename to src/test/kotlin/com/github/rushyverse/api/extension/RunnableExtTest.kt index b2eeb396..5eaede52 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/extension/RunnableExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/RunnableExtTest.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import kotlin.test.Test import kotlin.test.assertTrue diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/StringExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt similarity index 98% rename from src/test/kotlin/io/github/distractic/bukkit/api/extension/StringExtTest.kt rename to src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt index 00043e91..cd6a268c 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/extension/StringExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import io.github.distractic.bukkit.api.extension.* import org.junit.jupiter.api.DisplayName diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/VillagerExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/VillagerExtTest.kt similarity index 93% rename from src/test/kotlin/io/github/distractic/bukkit/api/extension/VillagerExtTest.kt rename to src/test/kotlin/com/github/rushyverse/api/extension/VillagerExtTest.kt index 0185517a..63fd814e 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/extension/VillagerExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/VillagerExtTest.kt @@ -1,6 +1,6 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension -import io.github.distractic.bukkit.api.Plugin +import com.github.rushyverse.api.Plugin import io.github.distractic.bukkit.api.utils.getRandomString import io.mockk.every import io.mockk.justRun @@ -16,11 +16,11 @@ import kotlin.test.* class VillagerExtTest { - private lateinit var plugin: Plugin + private lateinit var plugin: com.github.rushyverse.api.Plugin @BeforeTest fun onBefore() { - plugin = mockk() + plugin = mockk() every { plugin.name } returns "test" } diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/extension/WorldExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/WorldExtTest.kt similarity index 98% rename from src/test/kotlin/io/github/distractic/bukkit/api/extension/WorldExtTest.kt rename to src/test/kotlin/com/github/rushyverse/api/extension/WorldExtTest.kt index 95cdfe75..ab4c8e71 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/extension/WorldExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/WorldExtTest.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.extension +package com.github.rushyverse.api.extension import io.github.distractic.bukkit.api.utils.createRandomLocation import io.github.distractic.bukkit.api.utils.getRandomString diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/koin/CraftContextTest.kt b/src/test/kotlin/com/github/rushyverse/api/koin/CraftContextTest.kt similarity index 99% rename from src/test/kotlin/io/github/distractic/bukkit/api/koin/CraftContextTest.kt rename to src/test/kotlin/com/github/rushyverse/api/koin/CraftContextTest.kt index f77da6c2..fed60bc2 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/koin/CraftContextTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/koin/CraftContextTest.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.koin +package com.github.rushyverse.api.koin import io.github.distractic.bukkit.api.utils.getRandomString import org.junit.jupiter.api.DisplayName diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/listener/PlayerListenerTest.kt b/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt similarity index 98% rename from src/test/kotlin/io/github/distractic/bukkit/api/listener/PlayerListenerTest.kt rename to src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt index 438970c9..100bcd49 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/listener/PlayerListenerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.listener +package com.github.rushyverse.api.listener import io.github.distractic.bukkit.api.AbstractKoinTest import io.github.distractic.bukkit.api.player.Client diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/listener/VillagerListenerTest.kt b/src/test/kotlin/com/github/rushyverse/api/listener/VillagerListenerTest.kt similarity index 98% rename from src/test/kotlin/io/github/distractic/bukkit/api/listener/VillagerListenerTest.kt rename to src/test/kotlin/com/github/rushyverse/api/listener/VillagerListenerTest.kt index c0cf8fa3..92eda409 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/listener/VillagerListenerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/listener/VillagerListenerTest.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.listener +package com.github.rushyverse.api.listener import io.github.distractic.bukkit.api.AbstractKoinTest import io.github.distractic.bukkit.api.extension.namespacedKeyKeepJob diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImplTest.kt b/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt similarity index 99% rename from src/test/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImplTest.kt rename to src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt index d03e7765..249cbd60 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/player/ClientManagerImplTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.player +package com.github.rushyverse.api.player import io.github.distractic.bukkit.api.AbstractKoinTest import io.github.distractic.bukkit.api.player.exception.ClientNotFoundException diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/player/ClientTest.kt b/src/test/kotlin/com/github/rushyverse/api/player/ClientTest.kt similarity index 97% rename from src/test/kotlin/io/github/distractic/bukkit/api/player/ClientTest.kt rename to src/test/kotlin/com/github/rushyverse/api/player/ClientTest.kt index 0aed53a7..8499a7ae 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/player/ClientTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/player/ClientTest.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.player +package com.github.rushyverse.api.player import io.github.distractic.bukkit.api.AbstractKoinTest import io.mockk.every diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTaskTest.kt b/src/test/kotlin/com/github/rushyverse/api/schedule/SchedulerTaskTest.kt similarity index 99% rename from src/test/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTaskTest.kt rename to src/test/kotlin/com/github/rushyverse/api/schedule/SchedulerTaskTest.kt index 60c6acec..ad1c2631 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/schedule/SchedulerTaskTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/schedule/SchedulerTaskTest.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.schedule +package com.github.rushyverse.api.schedule import io.github.distractic.bukkit.api.utils.getRandomString import kotlinx.coroutines.* diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/utils/Generator.kt b/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt similarity index 93% rename from src/test/kotlin/io/github/distractic/bukkit/api/utils/Generator.kt rename to src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt index 8df39940..f0742d02 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/utils/Generator.kt +++ b/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.utils +package com.github.rushyverse.api.utils import org.bukkit.Location import org.bukkit.World diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/utils/LocationTestUtils.kt b/src/test/kotlin/com/github/rushyverse/api/utils/LocationTestUtils.kt similarity index 88% rename from src/test/kotlin/io/github/distractic/bukkit/api/utils/LocationTestUtils.kt rename to src/test/kotlin/com/github/rushyverse/api/utils/LocationTestUtils.kt index aaff2075..cde981f8 100644 --- a/src/test/kotlin/io/github/distractic/bukkit/api/utils/LocationTestUtils.kt +++ b/src/test/kotlin/com/github/rushyverse/api/utils/LocationTestUtils.kt @@ -1,4 +1,4 @@ -package io.github.distractic.bukkit.api.utils +package com.github.rushyverse.api.utils import org.bukkit.Location import kotlin.test.assertEquals diff --git a/src/test/kotlin/com/github/rushyverse/api/world/CuboidTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/CuboidTest.kt new file mode 100644 index 00000000..bf200ab0 --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/world/CuboidTest.kt @@ -0,0 +1,6 @@ +package com.github.rushyverse.api.world + +class CuboidTest { + + // TODO Write tests +} diff --git a/src/test/kotlin/io/github/distractic/bukkit/api/world/CuboidTest.kt b/src/test/kotlin/io/github/distractic/bukkit/api/world/CuboidTest.kt deleted file mode 100644 index 8c8275bf..00000000 --- a/src/test/kotlin/io/github/distractic/bukkit/api/world/CuboidTest.kt +++ /dev/null @@ -1,392 +0,0 @@ -package io.github.distractic.bukkit.api.world - -import io.github.distractic.bukkit.api.extension.minMax -import io.github.distractic.bukkit.api.utils.assertEqualsLocation -import io.github.distractic.bukkit.api.world.exception.WorldDifferentException -import io.mockk.every -import io.mockk.mockk -import org.bukkit.Location -import org.bukkit.World -import org.bukkit.block.Block -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.assertThrows -import java.util.* -import kotlin.random.Random -import kotlin.test.* - -class CuboidTest { - - @Nested - @DisplayName("Create instance") - inner class CreateInstance { - - @Nested - @DisplayName("From 2 locations") - inner class FromLocations { - - @Test - fun `with different world`() { - val world1 = mockk() - val world2 = mockk() - val location1 = Location(world1, .0, .0, .0) - val location2 = Location(world2, .0, .0, .0) - val exception = assertThrows { - Cuboid(location1, location2) - } - assertEquals(world1, exception.world1) - assertEquals(world2, exception.world2) - } - - @Test - fun `with minimal and maximal coordinates already separated`() { - val world = createMockWorld() - val randomCoords = createRandomPositionsOrdered() - val location1 = Location(world, randomCoords[0], randomCoords[1], randomCoords[2]) - val location2 = Location(world, randomCoords[3], randomCoords[4], randomCoords[5]) - - val cuboid = Cuboid(location1, location2) - assertEqualsLocation(location1, cuboid.startLocation) - assertEqualsLocation(location2, cuboid.endLocation) - } - - @Test - fun `minimal and maximal coordinates mixed`() { - val world = createMockWorld() - val randomCoords = createRandomPositionsOrdered() - val location1 = Location(world, randomCoords[0], randomCoords[4], randomCoords[2]) - val location2 = Location(world, randomCoords[3], randomCoords[1], randomCoords[5]) - - val cuboid = Cuboid(location1, location2) - val startLocation = cuboid.startLocation - val endLocation = cuboid.endLocation - - assertEquals(location1.x, startLocation.x) - assertEquals(location2.y, startLocation.y) - assertEquals(location1.z, startLocation.z) - - assertEquals(location2.x, endLocation.x) - assertEquals(location1.y, endLocation.y) - assertEquals(location2.z, endLocation.z) - } - } - } - - @Nested - @DisplayName("Get world") - inner class GetWorld { - - @Test - fun `is linked to the location's world instance from constructor`() { - val world = mockk() - val location1 = Location(world, .0, .0, .0) - val location2 = Location(world, .0, .0, .0) - val cuboid = Cuboid(location1, location2) - assertEquals(world, cuboid.world) - } - - @Test - fun `is null if location's world instance is null`() { - val location1 = Location(null, .0, .0, .0) - val location2 = Location(null, .0, .0, .0) - val cuboid = Cuboid(location1, location2) - assertNull(cuboid.world) - } - - @Test - fun `is not linked to the location's world instance if changed`() { - val world = mockk() - val location1 = Location(world, .0, .0, .0) - val location2 = Location(world, .0, .0, .0) - val cuboid = Cuboid(location1, location2) - assertEquals(world, cuboid.world) - - location1.world = null - assertEquals(world, cuboid.world) - - location2.world = null - assertEquals(world, cuboid.world) - } - } - - @Nested - @DisplayName("Set world") - inner class SetWorld { - - @Test - fun `change the world of start and end location`() { - val location1 = Location(null, .0, .0, .0) - val location2 = Location(null, .0, .0, .0) - val cuboid = Cuboid(location1, location2) - assertNull(cuboid.world) - - val world = mockk() - cuboid.world = world - assertEquals(world, cuboid.world) - assertEquals(world, cuboid.startLocation.world) - assertEquals(world, cuboid.endLocation.world) - } - } - - @Nested - @DisplayName("Contains location") - inner class ContainsLocation { - - private lateinit var world: World - private lateinit var location1: Location - private lateinit var location2: Location - private lateinit var cuboid: Cuboid - - @BeforeTest - fun onBefore() { - world = createMockWorld() - location1 = Location(world, -100.0, -100.0, -100.0) - location2 = Location(world, 100.0, 100.0, 100.0) - cuboid = Cuboid(location1, location2) - } - - @Test - fun `location is in bound of minimal coordinate`() { - assertTrue { cuboid.contains(location1) } - } - - @Test - fun `location is in bound of maximal coordinate`() { - assertTrue { cuboid.contains(location2) } - } - - @Test - fun `location is in bound but world is different`() { - val location = Location(mockk(), location1.x, location1.y, location1.z) - assertFalse { cuboid.contains(location) } - } - - @Test - fun `location between bounds`() { - fun getMiddle(min: Double, max: Double) = min + (max - min) - assertTrue { - cuboid.contains( - Location( - world, - getMiddle(location1.x, location2.x), - getMiddle(location1.y, location2.y), - getMiddle(location1.z, location2.z) - ) - ) - } - } - - @Nested - @DisplayName("X out of bounds") - inner class XOut { - - @Test - fun `inferior than minimal`() { - assertFalse { - cuboid.contains( - location1.add(-1.0, 0.0, 0.0) - ) - } - } - - @Test - fun `superior than maximal`() { - assertFalse { - cuboid.contains( - location2.add(1.0, 0.0, 0.0) - ) - } - } - } - - @Nested - @DisplayName("Y out of bounds") - inner class YOut { - - @Test - fun `location has y is inferior than minimal`() { - assertFalse { - cuboid.contains( - location1.add(0.0, -1.0, 0.0) - ) - } - } - - @Test - fun `location has y is superior than maximal`() { - assertFalse { - cuboid.contains( - location2.add(0.0, 1.0, 0.0) - ) - } - } - } - - @Nested - @DisplayName("Z out of bounds") - inner class ZOut { - - @Test - fun `location has z is inferior than minimal`() { - assertFalse { - cuboid.contains( - location1.add(0.0, 0.0, -1.0) - ) - } - } - - @Test - fun `location has z is superior than maximal`() { - assertFalse { - cuboid.contains( - location2.add(0.0, 0.0, 1.0) - ) - } - } - } - } - - @Nested - @DisplayName("Sequence generate") - inner class Sequence { - - @Nested - @DisplayName("Location") - inner class LocationSequence { - - @Test - fun `give only one location if min and max coordinate are equals`() { - val world = createMockWorld() - - val blockPosition = Random.nextInt(10000) - val position = blockPosition.toDouble() - val location1 = Location(world, position, position, position) - val location2 = Location(world, position, position, position) - val cuboid = Cuboid(location1, location2) - - val locations = cuboid.locationSequence().toList() - assertEquals(1, locations.size) - - val location = locations.first() - assertEquals(location.world, world) - assertEquals(location.blockX, blockPosition) - assertEquals(location.blockY, blockPosition) - assertEquals(location.blockZ, blockPosition) - } - - @Test - fun `give all locations if min and max coordinate are not equals`() { - val world = createMockWorld() - - val bound = 10.0 - - val location1 = - Location(world, Random.nextDouble(bound), Random.nextDouble(bound), Random.nextDouble(bound)) - val location2 = - Location(world, Random.nextDouble(bound), Random.nextDouble(bound), Random.nextDouble(bound)) - - val cuboid = Cuboid(location1, location2) - - val mocks = mutableListOf() - for (x in cuboid.startLocation.blockX..cuboid.endLocation.blockX) { - for (y in cuboid.startLocation.blockY..cuboid.endLocation.blockY) { - for (z in cuboid.startLocation.blockZ..cuboid.endLocation.blockZ) { - mocks += Location(world, x.toDouble(), y.toDouble(), z.toDouble()) - } - } - } - - val locations = cuboid.locationSequence().toList() - assertEquals(mocks, locations) - } - } - - @Nested - @DisplayName("Block") - inner class BlockSequence { - - @Test - fun `give only one block if min and max coordinate are equals`() { - val world = createMockWorld() - val blockMock = mockk() - - val blockPosition = Random.nextInt(10000) - val position = blockPosition.toDouble() - - every { - world.getBlockAt( - Location( - world, - position, - position, - position - ) - ) - } returns blockMock - - val location1 = Location(world, position, position, position) - val location2 = Location(world, position, position, position) - - val cuboid = Cuboid(location1, location2) - - val blocks = cuboid.blockSequence().toList() - assertEquals(1, blocks.size) - - val block = blocks.first() - assertEquals(blockMock, block) - } - - @Test - fun `give all locations if min and max coordinate are not equals`() { - val world = createMockWorld() - - val bound = 10.0 - - val location1 = - Location(world, Random.nextDouble(bound), Random.nextDouble(bound), Random.nextDouble(bound)) - val location2 = - Location(world, Random.nextDouble(bound), Random.nextDouble(bound), Random.nextDouble(bound)) - - val cuboid = Cuboid(location1, location2) - - val mocks = mutableListOf() - for (x in cuboid.startLocation.blockX..cuboid.endLocation.blockX) { - for (y in cuboid.startLocation.blockY..cuboid.endLocation.blockY) { - for (z in cuboid.startLocation.blockZ..cuboid.endLocation.blockZ) { - val blockMock = mockk() - val location = Location(world, x.toDouble(), y.toDouble(), z.toDouble()) - every { world.getBlockAt(location) } returns blockMock - mocks += blockMock - } - } - } - - val blocks = cuboid.blockSequence().toList() - assertEquals(mocks, blocks) - } - } - } - - private fun createMockWorld(): World { - val world = mockk() - every { world.uid } returns UUID.randomUUID() - return world - } - - private fun createRandomPositionsOrdered(): DoubleArray { - val bound = 1000.0 - val (minX, maxX) = minMax( - Random.nextDouble(-bound, bound), - Random.nextDouble(-bound, bound) - ) - val (minY, maxY) = minMax( - Random.nextDouble(-bound, bound), - Random.nextDouble(-bound, bound) - ) - val (minZ, maxZ) = minMax( - Random.nextDouble(-bound, bound), - Random.nextDouble(-bound, bound) - ) - return doubleArrayOf(minX, minY, minZ, maxX, maxY, maxZ) - } -} From bb242baf3fcdc414b67639c6ed353db7a005ef0b Mon Sep 17 00:00:00 2001 From: Quentin Date: Thu, 20 Jul 2023 19:21:27 +0200 Subject: [PATCH 042/143] Migrate project from @Distractic bukkit-api --- src/main/resources/api.properties | 0 src/main/resources/api_en_GB.properties | 0 src/main/resources/api_fr_FR.properties | 0 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/main/resources/api.properties delete mode 100644 src/main/resources/api_en_GB.properties delete mode 100644 src/main/resources/api_fr_FR.properties diff --git a/src/main/resources/api.properties b/src/main/resources/api.properties deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/resources/api_en_GB.properties b/src/main/resources/api_en_GB.properties deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/resources/api_fr_FR.properties b/src/main/resources/api_fr_FR.properties deleted file mode 100644 index e69de29b..00000000 From 3216a8082919d1c7f75ac131f9112708ad1c8278 Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 21 Jul 2023 11:57:43 +0200 Subject: [PATCH 043/143] Update README.md Replace Minstom by PaperMC --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ad9e1b37..acb68073 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # API -This project allows to create a Minestom server easily in Kotlin. It provides a lot of features to create a Minecraft +This project allows to create a PaperMC based server easily in Kotlin. It provides a lot of features to create a Minecraft server. ## Usage From d7e041af12e7e6c7ba8bc2d104e1bc68bcae5f99 Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 21 Jul 2023 16:53:53 +0200 Subject: [PATCH 044/143] chore: Added ext. functions for Cancellable components --- .../api/extension/event/_Cancellable.kt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/event/_Cancellable.kt diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/event/_Cancellable.kt b/src/main/kotlin/com/github/rushyverse/api/extension/event/_Cancellable.kt new file mode 100644 index 00000000..c3010bbe --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/extension/event/_Cancellable.kt @@ -0,0 +1,19 @@ +package com.github.rushyverse.api.extension.event + +import org.bukkit.event.Cancellable + +/** + * Extension function allowing to cancel the current process by method calling. + */ +public fun Cancellable.cancel() { + isCancelled = true +} + +/** + * Extension function allowing to cancel the current process by method calling with a condition. + */ +public fun T.cancelIf(condition: T.() -> Boolean) { + if (condition()) { + cancel() + } +} \ No newline at end of file From 9cc0a555d15d2572787030ccf853a32c3dac0b0e Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 21 Jul 2023 16:54:47 +0200 Subject: [PATCH 045/143] chore: Moved Listener ext. functions to appropriate package --- .../github/rushyverse/api/extension/{ => event}/_Listener.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/kotlin/com/github/rushyverse/api/extension/{ => event}/_Listener.kt (98%) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Listener.kt b/src/main/kotlin/com/github/rushyverse/api/extension/event/_Listener.kt similarity index 98% rename from src/main/kotlin/com/github/rushyverse/api/extension/_Listener.kt rename to src/main/kotlin/com/github/rushyverse/api/extension/event/_Listener.kt index da1aa938..0259b0e5 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_Listener.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/event/_Listener.kt @@ -1,4 +1,4 @@ -package com.github.rushyverse.api.extension +package com.github.rushyverse.api.extension.event import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeoutOrNull From 0f8e5cd10d254533d9c07368073d4d8489e7d8f7 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 22 Jul 2023 18:14:49 +0200 Subject: [PATCH 046/143] fix: Conflict merge --- LICENSE | 2 -- 1 file changed, 2 deletions(-) diff --git a/LICENSE b/LICENSE index 46104bba..8c14a885 100644 --- a/LICENSE +++ b/LICENSE @@ -1,8 +1,6 @@ MIT License -<<<<<<< HEAD Copyright (c) 2023 Rushyverse -======= Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 134a8e6c8fcada86799ef9cc7cf51f847ef27fcc Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 22 Jul 2023 18:15:52 +0200 Subject: [PATCH 047/143] fix(ci): Use same check for commit and bot --- .github/workflows/Check.yml | 8 +++----- .github/workflows/CheckDependabot.yml | 28 --------------------------- 2 files changed, 3 insertions(+), 33 deletions(-) delete mode 100644 .github/workflows/CheckDependabot.yml diff --git a/.github/workflows/Check.yml b/.github/workflows/Check.yml index 99f26163..f128840e 100644 --- a/.github/workflows/Check.yml +++ b/.github/workflows/Check.yml @@ -2,8 +2,6 @@ name: Check on: push: - branches-ignore: - - 'dependabot-**' paths-ignore: - '**.md' @@ -24,11 +22,11 @@ jobs: jdk: 17 - name: Build - uses: gradle/gradle-build-action@v2.3.2 + uses: gradle/gradle-build-action@v2.4.0 with: arguments: build -x test - name: Test - uses: gradle/gradle-build-action@v2.3.2 + uses: gradle/gradle-build-action@v2.4.0 with: - arguments: test \ No newline at end of file + arguments: test diff --git a/.github/workflows/CheckDependabot.yml b/.github/workflows/CheckDependabot.yml deleted file mode 100644 index f42b401d..00000000 --- a/.github/workflows/CheckDependabot.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Check-Dependabot - -on: - pull_request_target: - branches: - - 'dependabot-**' - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout project - uses: actions/checkout@v3 - - - name: Initialization - uses: ./.github/actions/init - with: - jdk: 17 - - - name: Build - uses: gradle/gradle-build-action@v2.3.2 - with: - arguments: build -x test - - - name: Test - uses: gradle/gradle-build-action@v2.3.2 - with: - arguments: test \ No newline at end of file From f2749c1265c67a46f90b4216f0d1124d1b4a0c30 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 22 Jul 2023 18:16:17 +0200 Subject: [PATCH 048/143] fix(conf): Change group project --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 06790684..fba0ae6a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -group=io.github.distractic +group=com.github.Rushyverse version=1.0.0 description= @@ -12,4 +12,4 @@ mccoroutineVersion=2.4.0 paperVersion=1.19-R0.1-SNAPSHOT junitVersion=5.9.0 mockkVersion=1.12.5 -slf4jVersion=2.0.0-alpha6 \ No newline at end of file +slf4jVersion=2.0.0-alpha6 From 1300128d03bfd7a47d62ca2720a2443cc3a0e424 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 22 Jul 2023 18:16:31 +0200 Subject: [PATCH 049/143] fix(conf): Change project name --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 88b7ddfc..c015b1d2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1 @@ -rootProject.name = "bukkit-api" \ No newline at end of file +rootProject.name = "api" From 01bf83b4d7ffd6110e5580969ef24f90dcbeb644 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 22 Jul 2023 18:36:54 +0200 Subject: [PATCH 050/143] chore(gradle): Update gradle version wrapper --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661e..c9eb15be 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-rc-1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From a9f428ab14ef7574479b85ad44ea671a949e2e79 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 22 Jul 2023 18:37:59 +0200 Subject: [PATCH 051/143] chore(deps): Move version declaration --- build.gradle.kts | 37 +++++++++++++++++-------------------- gradle.properties | 9 --------- 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 7587fafa..78e6d3c6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,8 @@ plugins { - kotlin("jvm") version "1.8.22" - kotlin("plugin.serialization") version "1.8.22" + embeddedKotlin("jvm") + embeddedKotlin("plugin.serialization") id("org.jetbrains.dokka") version "1.8.20" - id("com.github.johnrengelman.shadow") version "7.1.2" - id("net.researchgate.release") version "3.0.2" + id("com.github.johnrengelman.shadow") version "8.1.1" `maven-publish` `java-library` } @@ -15,16 +14,18 @@ repositories { maven("https://jitpack.io") } -val coroutineVersion: String by project -val loggingVersion: String by project -val koinVersion: String by project -val mccoroutineVersion: String by project -val paperVersion: String by project -val junitVersion: String by project -val mockkVersion: String by project -val slf4jVersion: String by project - dependencies { + val coroutineVersion = "1.6.4" + val loggingVersion = "2.1.23" + val koinVersion = "3.2.0" + val mccoroutineVersion = "2.4.0" + val paperVersion = "1.19-R0.1-SNAPSHOT" + val junitVersion = "5.9.0" + val mockkVersion = "1.12.5" + val slf4jVersion = "2.0.0-alpha6" + val fastboardVersion = "2.0.0" + val commandApiVersion = "9.0.3" + implementation(kotlin("stdlib")) implementation(kotlin("stdlib-jdk8")) implementation(kotlin("reflect")) @@ -44,13 +45,13 @@ dependencies { compileOnly("io.papermc.paper:paper-api:$paperVersion") // Scoreboard framework - implementation("fr.mrmicky:fastboard:2.0.0") + implementation("fr.mrmicky:fastboard:$fastboardVersion") // CommandAPI framework // The CommandAPI dependency used for Bukkit and it's forks - api("dev.jorel:commandapi-bukkit-core:9.0.3") + api("dev.jorel:commandapi-bukkit-core:$commandApiVersion") // Due to all functions available in the kotlindsl being inlined, we only need this dependency at compile-time - api("dev.jorel:commandapi-bukkit-kotlin:9.0.3") + api("dev.jorel:commandapi-bukkit-kotlin:$commandApiVersion") api("com.github.Rushyverse:core:6ae31a9250") @@ -178,7 +179,3 @@ publishing { } } } - -release { - tagTemplate.set("v${version}") -} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index fba0ae6a..cf58b4f0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,12 +4,3 @@ description= org.gradle.parallel = true kotlin.incremental = true - -coroutineVersion=1.6.4 -loggingVersion=2.1.23 -koinVersion=3.2.0 -mccoroutineVersion=2.4.0 -paperVersion=1.19-R0.1-SNAPSHOT -junitVersion=5.9.0 -mockkVersion=1.12.5 -slf4jVersion=2.0.0-alpha6 From 4abcf1d6831244b90590aac3547a622587f44118 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 22 Jul 2023 18:40:40 +0200 Subject: [PATCH 052/143] chore(gradle): Change configuration publish --- build.gradle.kts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 78e6d3c6..96625763 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -129,7 +129,7 @@ val javadocJar = tasks.register("javadocJar") { publishing { publications { - val projectOrganizationPath = "Distractic/${project.name}" + val projectOrganizationPath = "Rushyverse/${project.name}" val projectGitUrl = "https://github.com/$projectOrganizationPath" create(project.name) { @@ -159,6 +159,12 @@ publishing { } developers { + developer { + name.set("Quentixx") + email.set("Quentixx@outlook.fr") + url.set("https://github.com/Quentixx") + } + developer { name.set("Distractic") email.set("Distractic@outlook.fr") From 673128c68ffe57b76267b51116576d7acb9df422 Mon Sep 17 00:00:00 2001 From: Quentin Date: Mon, 24 Jul 2023 19:34:34 +0200 Subject: [PATCH 053/143] chore: Add object class API to save fastboards in memory --- .../kotlin/com/github/rushyverse/api/API.kt | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/main/kotlin/com/github/rushyverse/api/API.kt diff --git a/src/main/kotlin/com/github/rushyverse/api/API.kt b/src/main/kotlin/com/github/rushyverse/api/API.kt new file mode 100644 index 00000000..217d7c73 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/API.kt @@ -0,0 +1,30 @@ +package com.github.rushyverse.api + +import fr.mrmicky.fastboard.FastBoard +import org.bukkit.entity.Player + +public object API { + + private val fastBoards: MutableList = mutableListOf() + + /** + * Get or initialize a FastBoard for a given player. + * This approach can be useful for running multiple API-dependant plugins + * on the same server. + */ + public fun getOrInitFastBoard(player: Player): FastBoard { + var existingFastBoard = fastBoards.firstOrNull { it.player == player } + + if (existingFastBoard == null) { + existingFastBoard = FastBoard(player) + fastBoards.add(existingFastBoard) + println("API: FastBoards: Create new for ${player.name}") + } else { + println("API: FastBoards: Get existing for ${player.name}") + } + + println("API: FastBoards list: $fastBoards") + + return existingFastBoard + } +} \ No newline at end of file From d3cfbbb932ea603e88d7528c209ca0db180e1b75 Mon Sep 17 00:00:00 2001 From: Quentin Date: Mon, 24 Jul 2023 19:35:56 +0200 Subject: [PATCH 054/143] chore: Change client FastBoard declaration to an API memory call --- src/main/kotlin/com/github/rushyverse/api/player/Client.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/player/Client.kt b/src/main/kotlin/com/github/rushyverse/api/player/Client.kt index 417194b0..ab8bec73 100644 --- a/src/main/kotlin/com/github/rushyverse/api/player/Client.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/Client.kt @@ -1,5 +1,6 @@ package com.github.rushyverse.api.player +import com.github.rushyverse.api.API import fr.mrmicky.fastboard.FastBoard import com.github.rushyverse.api.delegate.DelegatePlayer import com.github.rushyverse.api.player.exception.PlayerNotFoundException @@ -23,7 +24,7 @@ public open class Client( public val player: Player? by DelegatePlayer(pluginId, playerUUID) - public val fastBoard: FastBoard by lazy { FastBoard(player) } + public val fastBoard: FastBoard by lazy { API.getOrInitFastBoard(requirePlayer()) } public var lang: SupportedLanguage = SupportedLanguage.ENGLISH From e806d45ec007612710f7d1bb9157c55ed7ecf169 Mon Sep 17 00:00:00 2001 From: Quentin Date: Mon, 24 Jul 2023 19:42:41 +0200 Subject: [PATCH 055/143] chore: Add method to delete an existing fastboard --- src/main/kotlin/com/github/rushyverse/api/API.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/API.kt b/src/main/kotlin/com/github/rushyverse/api/API.kt index 217d7c73..505ce313 100644 --- a/src/main/kotlin/com/github/rushyverse/api/API.kt +++ b/src/main/kotlin/com/github/rushyverse/api/API.kt @@ -18,13 +18,17 @@ public object API { if (existingFastBoard == null) { existingFastBoard = FastBoard(player) fastBoards.add(existingFastBoard) - println("API: FastBoards: Create new for ${player.name}") - } else { - println("API: FastBoards: Get existing for ${player.name}") } - println("API: FastBoards list: $fastBoards") - return existingFastBoard } + + /** + * Deletes and removes an existing FastBoard. + */ + public fun removeFastBoard(fastBoard: FastBoard) { + if (!fastBoard.isDeleted) + fastBoard.delete() + fastBoards.remove(fastBoard) + } } \ No newline at end of file From 13dd069ba8ee3ab8aad91f6715cb3704ad275823 Mon Sep 17 00:00:00 2001 From: Quentin Date: Mon, 24 Jul 2023 19:43:29 +0200 Subject: [PATCH 056/143] chore: Removes the fastboard of the player at on quit --- .../com/github/rushyverse/api/listener/PlayerListener.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt index 367a1094..6e40a2a9 100644 --- a/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt +++ b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt @@ -1,5 +1,6 @@ package com.github.rushyverse.api.listener +import com.github.rushyverse.api.API import com.github.rushyverse.api.Plugin import com.github.rushyverse.api.coroutine.exception.SilentCancellationException import com.github.rushyverse.api.koin.inject @@ -72,9 +73,10 @@ public class PlayerListener( plugin.clientEvents.onQuit(client, quitMessage) - client.fastBoard.delete() client.cancel(SilentCancellationException("The player ${player.name} (${player.uniqueId}) left")) event.quitMessage(quitMessage.get()) + + API.removeFastBoard(client.fastBoard) } } \ No newline at end of file From cb9701307e5f789a52214f18e246ce24cc461ac3 Mon Sep 17 00:00:00 2001 From: Distractic Date: Tue, 25 Jul 2023 23:59:50 +0200 Subject: [PATCH 057/143] tests: Fix import --- .../rushyverse/api/extension/ItemStackExtTest.kt | 4 ++-- .../rushyverse/api/extension/JavaPluginExtTest.kt | 8 ++++---- .../rushyverse/api/extension/LocationExtTest.kt | 5 ++--- .../api/extension/MerchantRecipeExtTest.kt | 4 ++-- .../api/extension/PersistentDataHolderTest.kt | 4 ++-- .../rushyverse/api/extension/PlayerExtTest.kt | 4 ++-- .../rushyverse/api/extension/StringExtTest.kt | 3 +-- .../rushyverse/api/extension/VillagerExtTest.kt | 5 ++--- .../rushyverse/api/extension/WorldExtTest.kt | 6 +++--- .../github/rushyverse/api/koin/CraftContextTest.kt | 4 ++-- .../rushyverse/api/listener/PlayerListenerTest.kt | 14 +++++++------- .../api/listener/VillagerListenerTest.kt | 8 ++++---- .../rushyverse/api/player/ClientManagerImplTest.kt | 8 ++++---- .../com/github/rushyverse/api/player/ClientTest.kt | 7 ++++--- .../rushyverse/api/schedule/SchedulerTaskTest.kt | 4 ++-- 15 files changed, 43 insertions(+), 45 deletions(-) diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/ItemStackExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/ItemStackExtTest.kt index 544bfd63..f2c8a114 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/ItemStackExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/ItemStackExtTest.kt @@ -1,6 +1,6 @@ package com.github.rushyverse.api.extension -import io.github.distractic.bukkit.api.utils.getRandomString +import com.github.rushyverse.api.utils.getRandomString import io.mockk.every import io.mockk.mockk import org.bukkit.Material @@ -323,4 +323,4 @@ class ItemStackExtTest { every { type } returns material } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/JavaPluginExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/JavaPluginExtTest.kt index 534d26e2..103e0f86 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/JavaPluginExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/JavaPluginExtTest.kt @@ -1,8 +1,8 @@ package com.github.rushyverse.api.extension -import io.github.distractic.bukkit.api.item.CraftSlot -import io.github.distractic.bukkit.api.item.exception.CraftResultMissingException -import io.github.distractic.bukkit.api.utils.getRandomString +import com.github.rushyverse.api.item.CraftSlot +import com.github.rushyverse.api.item.exception.CraftResultMissingException +import com.github.rushyverse.api.utils.getRandomString import io.mockk.every import io.mockk.mockk import io.mockk.slot @@ -165,4 +165,4 @@ class JavaPluginExtTest { } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/LocationExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/LocationExtTest.kt index 095a8e08..6a35bfbb 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/LocationExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/LocationExtTest.kt @@ -1,7 +1,6 @@ package com.github.rushyverse.api.extension -import io.github.distractic.bukkit.api.extension.* -import io.github.distractic.bukkit.api.utils.getRandomString +import com.github.rushyverse.api.utils.getRandomString import io.mockk.mockk import org.bukkit.Location import org.bukkit.World @@ -139,4 +138,4 @@ class LocationExtTest { assertEquals(Location(world, x, y, z, yaw, pitch), loc.copy(world, x, y, z, yaw, pitch)) } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/MerchantRecipeExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/MerchantRecipeExtTest.kt index 75829ecd..d1513c02 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/MerchantRecipeExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/MerchantRecipeExtTest.kt @@ -1,6 +1,6 @@ package com.github.rushyverse.api.extension -import io.github.distractic.bukkit.api.utils.getRandomString +import com.github.rushyverse.api.utils.getRandomString import io.mockk.mockk import org.bukkit.Material import org.bukkit.inventory.ItemStack @@ -83,4 +83,4 @@ class MerchantRecipeExtTest { assertEquals(false, recipe.shouldIgnoreDiscounts()) assertEquals(emptyList(), recipe.ingredients) } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/PersistentDataHolderTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/PersistentDataHolderTest.kt index e814076a..44557e22 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/PersistentDataHolderTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/PersistentDataHolderTest.kt @@ -1,6 +1,6 @@ package com.github.rushyverse.api.extension -import io.github.distractic.bukkit.api.utils.getRandomString +import com.github.rushyverse.api.utils.getRandomString import io.mockk.mockk import org.bukkit.persistence.PersistentDataContainer import org.bukkit.persistence.PersistentDataHolder @@ -20,4 +20,4 @@ class PersistentDataHolderTest { assertTrue { isEquals } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/PlayerExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/PlayerExtTest.kt index 90c6a393..f11a7ca4 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/PlayerExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/PlayerExtTest.kt @@ -1,7 +1,7 @@ package com.github.rushyverse.api.extension import com.destroystokyo.paper.profile.PlayerProfile -import io.github.distractic.bukkit.api.utils.getRandomString +import com.github.rushyverse.api.utils.getRandomString import io.mockk.every import io.mockk.justRun import io.mockk.mockk @@ -100,4 +100,4 @@ class PlayerExtTest { } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt index cd6a268c..1b24f813 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt @@ -1,6 +1,5 @@ package com.github.rushyverse.api.extension -import io.github.distractic.bukkit.api.extension.* import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.assertThrows @@ -125,4 +124,4 @@ class StringExtTest { } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/VillagerExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/VillagerExtTest.kt index 63fd814e..5541e5b4 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/VillagerExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/VillagerExtTest.kt @@ -1,7 +1,6 @@ package com.github.rushyverse.api.extension -import com.github.rushyverse.api.Plugin -import io.github.distractic.bukkit.api.utils.getRandomString +import com.github.rushyverse.api.utils.getRandomString import io.mockk.every import io.mockk.justRun import io.mockk.mockk @@ -85,4 +84,4 @@ class VillagerExtTest { } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/WorldExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/WorldExtTest.kt index ab4c8e71..f93c14d9 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/WorldExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/WorldExtTest.kt @@ -1,7 +1,7 @@ package com.github.rushyverse.api.extension -import io.github.distractic.bukkit.api.utils.createRandomLocation -import io.github.distractic.bukkit.api.utils.getRandomString +import com.github.rushyverse.api.utils.createRandomLocation +import com.github.rushyverse.api.utils.getRandomString import io.mockk.every import io.mockk.mockk import io.mockk.slot @@ -92,4 +92,4 @@ class WorldExtTest { assertFalse { slotGen.captured } assertFalse { slotUrgent.captured } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/koin/CraftContextTest.kt b/src/test/kotlin/com/github/rushyverse/api/koin/CraftContextTest.kt index fed60bc2..463fd5b8 100644 --- a/src/test/kotlin/com/github/rushyverse/api/koin/CraftContextTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/koin/CraftContextTest.kt @@ -1,6 +1,6 @@ package com.github.rushyverse.api.koin -import io.github.distractic.bukkit.api.utils.getRandomString +import com.github.rushyverse.api.utils.getRandomString import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @@ -338,4 +338,4 @@ class CraftContextTest { assertTrue { isInit } } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt b/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt index 100bcd49..d5b58fbf 100644 --- a/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt @@ -1,11 +1,11 @@ package com.github.rushyverse.api.listener -import io.github.distractic.bukkit.api.AbstractKoinTest -import io.github.distractic.bukkit.api.player.Client -import io.github.distractic.bukkit.api.player.ClientManager -import io.github.distractic.bukkit.api.player.ClientManagerImpl -import io.github.distractic.bukkit.api.player.exception.ClientAlreadyExistsException -import io.github.distractic.bukkit.api.utils.getRandomString +import com.github.rushyverse.api.AbstractKoinTest +import com.github.rushyverse.api.player.Client +import com.github.rushyverse.api.player.ClientManager +import com.github.rushyverse.api.player.ClientManagerImpl +import com.github.rushyverse.api.player.exception.ClientAlreadyExistsException +import com.github.rushyverse.api.utils.getRandomString import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.CoroutineScope @@ -128,4 +128,4 @@ class PlayerListenerTest : AbstractKoinTest() { private fun createClient(player: Player) = Client(pluginId, player.uniqueId, CoroutineScope(Dispatchers.Default + SupervisorJob())) -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/listener/VillagerListenerTest.kt b/src/test/kotlin/com/github/rushyverse/api/listener/VillagerListenerTest.kt index 92eda409..15fe4fcf 100644 --- a/src/test/kotlin/com/github/rushyverse/api/listener/VillagerListenerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/listener/VillagerListenerTest.kt @@ -1,8 +1,8 @@ package com.github.rushyverse.api.listener -import io.github.distractic.bukkit.api.AbstractKoinTest -import io.github.distractic.bukkit.api.extension.namespacedKeyKeepJob -import io.github.distractic.bukkit.api.utils.getRandomString +import com.github.rushyverse.api.AbstractKoinTest +import com.github.rushyverse.api.extension.namespacedKeyKeepJob +import com.github.rushyverse.api.utils.getRandomString import io.mockk.every import io.mockk.mockk import io.mockk.slot @@ -70,4 +70,4 @@ class VillagerListenerTest : AbstractKoinTest() { } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt b/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt index 249cbd60..59650757 100644 --- a/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt @@ -1,8 +1,8 @@ package com.github.rushyverse.api.player -import io.github.distractic.bukkit.api.AbstractKoinTest -import io.github.distractic.bukkit.api.player.exception.ClientNotFoundException -import io.github.distractic.bukkit.api.utils.getRandomString +import com.github.rushyverse.api.AbstractKoinTest +import com.github.rushyverse.api.player.exception.ClientNotFoundException +import com.github.rushyverse.api.utils.getRandomString import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.CoroutineScope @@ -170,4 +170,4 @@ class ClientManagerImplTest : AbstractKoinTest() { private fun createClient(player: Player) = Client(pluginId, player.uniqueId, CoroutineScope(Dispatchers.Default + SupervisorJob())) -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/player/ClientTest.kt b/src/test/kotlin/com/github/rushyverse/api/player/ClientTest.kt index 8499a7ae..92b8b5ba 100644 --- a/src/test/kotlin/com/github/rushyverse/api/player/ClientTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/player/ClientTest.kt @@ -1,6 +1,7 @@ package com.github.rushyverse.api.player -import io.github.distractic.bukkit.api.AbstractKoinTest +import com.github.rushyverse.api.AbstractKoinTest +import com.github.rushyverse.api.player.exception.PlayerNotFoundException import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.CoroutineScope @@ -35,7 +36,7 @@ class ClientTest : AbstractKoinTest() { fun `require player instance not found throws an exception`() { val client = Client(pluginId, UUID.randomUUID(), CoroutineScope(EmptyCoroutineContext)) every { server.getPlayer(any()) } returns null - assertThrows { + assertThrows { client.requirePlayer() } } @@ -49,4 +50,4 @@ class ClientTest : AbstractKoinTest() { every { server.getPlayer(uuid) } returns player assertEquals(player, client.requirePlayer()) } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/schedule/SchedulerTaskTest.kt b/src/test/kotlin/com/github/rushyverse/api/schedule/SchedulerTaskTest.kt index ad1c2631..b8189b1c 100644 --- a/src/test/kotlin/com/github/rushyverse/api/schedule/SchedulerTaskTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/schedule/SchedulerTaskTest.kt @@ -1,6 +1,6 @@ package com.github.rushyverse.api.schedule -import io.github.distractic.bukkit.api.utils.getRandomString +import com.github.rushyverse.api.utils.getRandomString import kotlinx.coroutines.* import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested @@ -453,4 +453,4 @@ class SchedulerTaskTest { } private fun scope() = CoroutineScope(Dispatchers.IO + SupervisorJob()) -} \ No newline at end of file +} From 6c765141ffb19ee8ac2ecead7520bc057bb4c1a0 Mon Sep 17 00:00:00 2001 From: Distractic Date: Wed, 26 Jul 2023 00:00:13 +0200 Subject: [PATCH 058/143] feat: Refactor and add extension functions --- .../kotlin/com/github/rushyverse/api/API.kt | 34 ---- .../com/github/rushyverse/api/APIPlugin.kt | 21 ++- .../com/github/rushyverse/api/Plugin.kt | 7 +- .../com/github/rushyverse/api/SharedMemory.kt | 14 -- .../rushyverse/api/extension/_Component.kt | 170 +++++++++++++++++- .../rushyverse/api/extension/_Location.kt | 14 +- .../github/rushyverse/api/extension/_Math.kt | 15 ++ .../rushyverse/api/extension/_Number.kt | 58 ++++++ .../rushyverse/api/extension/_String.kt | 4 +- .../rushyverse/api/game/SharedGameData.kt | 4 +- .../github/rushyverse/api/game/stats/Stats.kt | 4 +- .../rushyverse/api/koin/CraftContext.kt | 2 +- .../rushyverse/api/listener/PlayerListener.kt | 28 +-- .../github/rushyverse/api/player/Client.kt | 26 +-- .../api/player/PluginClientEvents.kt | 31 ---- .../player/scoreboard/ScoreboardManager.kt | 25 +++ .../api/schedule/AbstractScheduler.kt | 7 +- .../rushyverse/api/schedule/SchedulerTask.kt | 14 +- .../ResourceBundleTranslationsProvider.kt | 4 +- .../github/rushyverse/api/world/CubeArea.kt | 40 +++++ .../com/github/rushyverse/api/world/Cuboid.kt | 39 ---- .../com/github/rushyverse/api/world/Pos.kt | 36 ---- 22 files changed, 377 insertions(+), 220 deletions(-) delete mode 100644 src/main/kotlin/com/github/rushyverse/api/API.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/SharedMemory.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/_Math.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/_Number.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/player/PluginClientEvents.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/player/scoreboard/ScoreboardManager.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/world/Cuboid.kt delete mode 100644 src/main/kotlin/com/github/rushyverse/api/world/Pos.kt diff --git a/src/main/kotlin/com/github/rushyverse/api/API.kt b/src/main/kotlin/com/github/rushyverse/api/API.kt deleted file mode 100644 index 505ce313..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/API.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.github.rushyverse.api - -import fr.mrmicky.fastboard.FastBoard -import org.bukkit.entity.Player - -public object API { - - private val fastBoards: MutableList = mutableListOf() - - /** - * Get or initialize a FastBoard for a given player. - * This approach can be useful for running multiple API-dependant plugins - * on the same server. - */ - public fun getOrInitFastBoard(player: Player): FastBoard { - var existingFastBoard = fastBoards.firstOrNull { it.player == player } - - if (existingFastBoard == null) { - existingFastBoard = FastBoard(player) - fastBoards.add(existingFastBoard) - } - - return existingFastBoard - } - - /** - * Deletes and removes an existing FastBoard. - */ - public fun removeFastBoard(fastBoard: FastBoard) { - if (!fastBoard.isDeleted) - fastBoard.delete() - fastBoards.remove(fastBoard) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt b/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt index 5dfc6d18..32c34e66 100644 --- a/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt +++ b/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt @@ -1,8 +1,27 @@ package com.github.rushyverse.api +import com.github.rushyverse.api.koin.CraftContext +import com.github.rushyverse.api.koin.loadModule +import com.github.rushyverse.api.player.scoreboard.ScoreboardManager import org.bukkit.plugin.java.JavaPlugin /** * Plugin to enable the API in server. */ -public class APIPlugin : JavaPlugin() \ No newline at end of file +public class APIPlugin : JavaPlugin() { + + public companion object { + public const val ID: String = "api" + } + + override fun onEnable() { + CraftContext.startKoin(ID) + loadModule(ID) { + single { ScoreboardManager() } + } + } + + override fun onDisable() { + CraftContext.stopKoin(ID) + } +} diff --git a/src/main/kotlin/com/github/rushyverse/api/Plugin.kt b/src/main/kotlin/com/github/rushyverse/api/Plugin.kt index 49706033..8601a94a 100644 --- a/src/main/kotlin/com/github/rushyverse/api/Plugin.kt +++ b/src/main/kotlin/com/github/rushyverse/api/Plugin.kt @@ -18,7 +18,6 @@ import java.util.* public abstract class Plugin : SuspendingJavaPlugin() { public abstract val id: String - public abstract val clientEvents: PluginClientEvents public companion object { public const val BUNDLE_API: String = "api_translate" @@ -34,7 +33,7 @@ public abstract class Plugin : SuspendingJavaPlugin() { registerListener { VillagerListener(this) } } - protected inline fun modulePlugin(): Module = loadModule(id) { + protected inline fun modulePlugin(): Module = loadModule(id) { single { this@Plugin } single { this@Plugin as T } } @@ -67,6 +66,6 @@ public abstract class Plugin : SuspendingJavaPlugin() { */ protected open suspend fun createTranslationsProvider(): ResourceBundleTranslationsProvider = ResourceBundleTranslationsProvider().apply { - registerResourceBundleForSupportedLocales(com.github.rushyverse.api.Plugin.Companion.BUNDLE_API, ResourceBundle::getBundle) + registerResourceBundleForSupportedLocales(Plugin.Companion.BUNDLE_API, ResourceBundle::getBundle) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/SharedMemory.kt b/src/main/kotlin/com/github/rushyverse/api/SharedMemory.kt deleted file mode 100644 index 932ae71a..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/SharedMemory.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.rushyverse.api - -import com.github.rushyverse.api.game.SharedGameData - -/** - * Ready to use Singleton object that represents a shared memory space that facilitates data exchange - * between the API and the server plugins that rely on the API. - */ -public object SharedMemory { - - // Holds shared game-related data - public val games: SharedGameData = SharedGameData() -} - diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Component.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Component.kt index 47ec9af6..60d73bbc 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_Component.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Component.kt @@ -1,15 +1,11 @@ package com.github.rushyverse.api.extension import net.kyori.adventure.text.* +import net.kyori.adventure.text.format.TextDecoration import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer import kotlin.contracts.InvocationKind import kotlin.contracts.contract -@Suppress("NOTHING_TO_INLINE") -public inline fun Component.toText(): String { - return LegacyComponentSerializer.legacySection().serialize(this) -} - /** * Creates a text component using a builder. * @param builder Function to build the component with the component builder. @@ -79,4 +75,166 @@ public inline fun entityNBT(builder: EntityNBTComponent.Builder.() -> Unit): Ent public inline fun translatable(builder: TranslatableComponent.Builder.() -> Unit): TranslatableComponent { contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } return Component.translatable().apply(builder).build() -} \ No newline at end of file +} + +/** + * Transform a component into a legacy text. + * @receiver Component to transform. + * @return The legacy text. + */ +public fun Component.toText(): String = LegacyComponentSerializer.legacySection().serialize(this) + +/** + * Add the [bold][TextDecoration.BOLD] decoration to the component. + * @receiver Component to transform. + * @return The same component. + */ +public fun Component.withBold(): Component = this.decoration(TextDecoration.BOLD, TextDecoration.State.TRUE) + +/** + * Remove the [bold][TextDecoration.BOLD] decoration to the component. + * @receiver Component to transform. + * @return The same component. + */ +public fun Component.withoutBold(): Component = this.decoration(TextDecoration.BOLD, TextDecoration.State.FALSE) + +/** + * Set as [not set][TextDecoration.State.NOT_SET] the [bold][TextDecoration.BOLD] decoration of the component. + * @receiver Component to transform. + * @return The same component. + */ +public fun Component.undefineBold(): Component = this.decoration(TextDecoration.BOLD, TextDecoration.State.NOT_SET) + +/** + * Add the [italic][TextDecoration.ITALIC] decoration to the component. + * @receiver Component to transform. + * @return The same component. + */ +public fun Component.withItalic(): Component = this.decoration(TextDecoration.ITALIC, TextDecoration.State.TRUE) + +/** + * Remove the [italic][TextDecoration.ITALIC] decoration to the component. + * @receiver Component to transform. + * @return The same component. + */ +public fun Component.withoutItalic(): Component = this.decoration(TextDecoration.ITALIC, TextDecoration.State.FALSE) + +/** + * Set as [not set][TextDecoration.State.NOT_SET] the [italic][TextDecoration.ITALIC] decoration of the component. + * @receiver Component to transform. + * @return The same component. + */ +public fun Component.undefineItalic(): Component = this.decoration(TextDecoration.ITALIC, TextDecoration.State.NOT_SET) + +/** + * Add the [underlined][TextDecoration.UNDERLINED] decoration to the component. + * @receiver Component to transform. + * @return The same component. + */ +public fun Component.withUnderlined(): Component = this.decoration(TextDecoration.UNDERLINED, TextDecoration.State.TRUE) + +/** + * Remove the [underlined][TextDecoration.UNDERLINED] decoration to the component. + * @receiver Component to transform. + * @return The same component. + */ +public fun Component.withoutUnderlined(): Component = + this.decoration(TextDecoration.UNDERLINED, TextDecoration.State.FALSE) + +/** + * Set as [not set][TextDecoration.State.NOT_SET] the [underlined][TextDecoration.UNDERLINED] decoration of the component. + * @receiver Component to transform. + * @return The same component. + */ +public fun Component.undefineUnderlined(): Component = + this.decoration(TextDecoration.UNDERLINED, TextDecoration.State.NOT_SET) + +/** + * Add the [strikethrough][TextDecoration.STRIKETHROUGH] decoration to the component. + * @receiver Component to transform. + * @return The same component. + */ +public fun Component.withStrikethrough(): Component = + this.decoration(TextDecoration.STRIKETHROUGH, TextDecoration.State.TRUE) + +/** + * Remove the [strikethrough][TextDecoration.STRIKETHROUGH] decoration to the component. + * @receiver Component to transform. + * @return The same component. + */ +public fun Component.withoutStrikethrough(): Component = + this.decoration(TextDecoration.STRIKETHROUGH, TextDecoration.State.FALSE) + +/** + * Set as [not set][TextDecoration.State.NOT_SET] the [strikethrough][TextDecoration.STRIKETHROUGH] decoration of the component. + * @receiver Component to transform. + * @return The same component. + */ +public fun Component.undefineStrikethrough(): Component = + this.decoration(TextDecoration.STRIKETHROUGH, TextDecoration.State.NOT_SET) + +/** + * Add the [obfuscated][TextDecoration.OBFUSCATED] decoration to the component. + * @receiver Component to transform. + * @return The same component. + */ +public fun Component.withObfuscated(): Component = this.decoration(TextDecoration.OBFUSCATED, TextDecoration.State.TRUE) + +/** + * Remove the [obfuscated][TextDecoration.OBFUSCATED] decoration to the component. + * @receiver Component to transform. + * @return The same component. + */ +public fun Component.withoutObfuscated(): Component = + this.decoration(TextDecoration.OBFUSCATED, TextDecoration.State.FALSE) + +/** + * Set as [not set][TextDecoration.State.NOT_SET] the [obfuscated][TextDecoration.OBFUSCATED] decoration of the component. + * @receiver Component to transform. + * @return The same component. + */ +public fun Component.undefineObfuscated(): Component = + this.decoration(TextDecoration.OBFUSCATED, TextDecoration.State.NOT_SET) + +/** + * Add all decorations to the component. + * @receiver Component to transform. + * @return The same component. + */ +public fun Component.withDecorations(): Component = + withBold().withItalic().withUnderlined().withStrikethrough().withObfuscated() + +/** + * Remove all decorations from the component. + * @receiver Component to transform. + * @return The same component. + */ +public fun Component.withoutDecorations(): Component = + withoutBold().withoutItalic().withoutUnderlined().withoutStrikethrough().withoutObfuscated() + +/** + * Set as [not set][TextDecoration.State.NOT_SET] all decorations of the component. + * @receiver Component to transform. + * @return The same component. + */ +public fun Component.undefineDecorations(): Component = + undefineBold().undefineItalic().undefineUnderlined().undefineStrikethrough().undefineObfuscated() + +/** + * Create a new component using the string content. + * @receiver String to transform. + * @param extractUrls If true, will extract urls from the string and apply a clickable effect on them. + * @param extractColors If true, will extract colors from the string and apply them to the component. + * @param colorChar The character used to define a color. + * @return A new text component. + */ +public fun String.toComponent( + extractUrls: Boolean = false, + extractColors: Boolean = true, + colorChar: Char = LegacyComponentSerializer.AMPERSAND_CHAR, +): TextComponent = LegacyComponentSerializer.builder().apply { + if (extractUrls) extractUrls() + if (extractColors) character(colorChar) +} + .build() + .deserialize(this) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Location.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Location.kt index 181841f4..82cb9ffe 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_Location.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Location.kt @@ -1,6 +1,5 @@ package com.github.rushyverse.api.extension -import com.github.rushyverse.api.world.Pos import org.bukkit.Location import org.bukkit.World @@ -88,4 +87,15 @@ public fun Location.copy( pitch: Float = this.pitch, ): Location = Location(world, x, y, z, yaw, pitch) -public fun Location.toPos(): Pos = Pos(x, y, z, yaw, pitch) \ No newline at end of file +public fun Location.divide(value: Number): Location { + val toDouble = value.toDouble() + return copy(x = x / toDouble, y = y / toDouble, z = z / toDouble) +} + +/** + * Returns the center position between two points. + * @receiver The first position. + * @param other The second position. + * @return The center position between the two points. + */ +public fun Location.centerRelative(other: Location): Location = add(other).divide(2) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Math.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Math.kt new file mode 100644 index 00000000..c04d4742 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Math.kt @@ -0,0 +1,15 @@ +package com.github.rushyverse.api.extension + +/** + * Get the lowest and highest value in a specific index. + * The lowest value is placed at the index 0 and highest at the index 1. + * + * @param a First value. + * @param b Second value. + * @return Both values with a defined order. + */ +public fun > minMaxOf(a: T, b: T): Pair = if (a <= b) { + a to b +} else { + b to a +} diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Number.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Number.kt new file mode 100644 index 00000000..e4c54cbd --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Number.kt @@ -0,0 +1,58 @@ +package com.github.rushyverse.api.extension + +/** + * The values of roman number linked to their roman numerals. + * The values are ordered from the largest to the smallest. + */ +public val ROMAN_NUMERALS_VALUES: Map = mapOf( + 1000 to "M", + 900 to "CM", + 500 to "D", + 400 to "CD", + 100 to "C", + 90 to "XC", + 50 to "L", + 40 to "XL", + 10 to "X", + 9 to "IX", + 5 to "V", + 4 to "IV", + 1 to "I" +) + +/** + * The roman numerals. + * The numerals are ordered from the largest to the smallest. + * @see ROMAN_VALUES + */ +public val ROMAN_NUMERALS: Array = ROMAN_NUMERALS_VALUES.values.toTypedArray() + +/** + * The values of roman number. + * The values are ordered from the largest to the smallest. + * @see ROMAN_NUMERALS + */ +public val ROMAN_VALUES: IntArray = ROMAN_NUMERALS_VALUES.keys.toIntArray() + +/** + * Convert a number to roman numerals. + * @receiver Int between 1 and 3999. + * @return A string of roman numerals. + */ +public fun Int.toRomanNumerals(): String { + require(this > 0) { "Number must be positive" } + require(this < 4000) { "Number must be less than 4000" } + + var remaining = this + var i = 0 + return buildString { + while(remaining > 0) { + val romanValue = ROMAN_VALUES[i] + repeat(remaining / romanValue) { + append(ROMAN_NUMERALS[i]) + remaining -= romanValue + } + i++ + } + } +} diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt index c6065682..3d21ffa8 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt @@ -204,5 +204,5 @@ public fun String.toFormattedLoreSequence(lineLength: Int = DEFAULT_LORE_LINE_LE * @param tagResolver The tag resolver used to resolve the custom tags. * @return The component created from the string. */ -public fun String.asMiniComponent(vararg tagResolver: TagResolver): Component = - MiniMessage.miniMessage().deserialize(this, *tagResolver) \ No newline at end of file +public fun String.toComponent(vararg tagResolver: TagResolver): Component = + MiniMessage.miniMessage().deserialize(this, *tagResolver) diff --git a/src/main/kotlin/com/github/rushyverse/api/game/SharedGameData.kt b/src/main/kotlin/com/github/rushyverse/api/game/SharedGameData.kt index a46ab13c..462ca0f8 100644 --- a/src/main/kotlin/com/github/rushyverse/api/game/SharedGameData.kt +++ b/src/main/kotlin/com/github/rushyverse/api/game/SharedGameData.kt @@ -49,8 +49,6 @@ public class SharedGameData { games[index] = gameData } - println("Shared memory data : $games") - callOnChange() } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/game/stats/Stats.kt b/src/main/kotlin/com/github/rushyverse/api/game/stats/Stats.kt index a9932944..54eefca8 100644 --- a/src/main/kotlin/com/github/rushyverse/api/game/stats/Stats.kt +++ b/src/main/kotlin/com/github/rushyverse/api/game/stats/Stats.kt @@ -1,5 +1,5 @@ package com.github.rushyverse.api.game.stats -public interface Stats { +public fun interface Stats { public fun calculateScore(): Int -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/koin/CraftContext.kt b/src/main/kotlin/com/github/rushyverse/api/koin/CraftContext.kt index be43a734..ab9a98f3 100644 --- a/src/main/kotlin/com/github/rushyverse/api/koin/CraftContext.kt +++ b/src/main/kotlin/com/github/rushyverse/api/koin/CraftContext.kt @@ -142,4 +142,4 @@ public object CraftContext { public fun unloadKoinModules(id: String, modules: List): Unit = synchronized(this) { get(id).unloadModules(modules) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt index 6e40a2a9..94555df3 100644 --- a/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt +++ b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt @@ -1,21 +1,19 @@ package com.github.rushyverse.api.listener -import com.github.rushyverse.api.API +import com.github.rushyverse.api.APIPlugin import com.github.rushyverse.api.Plugin import com.github.rushyverse.api.coroutine.exception.SilentCancellationException import com.github.rushyverse.api.koin.inject import com.github.rushyverse.api.player.* import com.github.rushyverse.api.player.exception.ClientAlreadyExistsException +import com.github.rushyverse.api.player.scoreboard.ScoreboardManager import kotlinx.coroutines.cancel -import net.kyori.adventure.text.Component import org.bukkit.entity.Player import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority import org.bukkit.event.Listener import org.bukkit.event.player.PlayerJoinEvent import org.bukkit.event.player.PlayerQuitEvent -import java.util.concurrent.atomic.AtomicReference -import java.util.logging.Logger /** * Main listener to manager instance of clients for the player entering and exiting the server. @@ -27,7 +25,7 @@ public class PlayerListener( ) : Listener { private val clients: ClientManager by inject(plugin.id) - private val logger: Logger by inject(plugin.id) + private val scoreboardManager: ScoreboardManager by inject(APIPlugin.ID) /** * Handle the join event to create and store a new client. @@ -36,14 +34,8 @@ public class PlayerListener( */ @EventHandler(priority = EventPriority.LOWEST) public suspend fun onJoin(event: PlayerJoinEvent) { - println("Player join $event for plugin $plugin") val player = event.player - val clientCreated = createAndSaveClient(player) - val joinMessage = AtomicReference(event.joinMessage()) - - plugin.clientEvents.onJoin(clientCreated, joinMessage) - - event.joinMessage(joinMessage.get()) + createAndSaveClient(player) } /** @@ -68,15 +60,9 @@ public class PlayerListener( @EventHandler(priority = EventPriority.HIGHEST) public suspend fun onQuit(event: PlayerQuitEvent) { val player = event.player - val client = clients.removeClient(player) ?: return - val quitMessage = AtomicReference(event.quitMessage()) - - plugin.clientEvents.onQuit(client, quitMessage) + scoreboardManager.remove(player) + val client = clients.removeClient(player) ?: return client.cancel(SilentCancellationException("The player ${player.name} (${player.uniqueId}) left")) - - event.quitMessage(quitMessage.get()) - - API.removeFastBoard(client.fastBoard) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/player/Client.kt b/src/main/kotlin/com/github/rushyverse/api/player/Client.kt index ab8bec73..216f58bc 100644 --- a/src/main/kotlin/com/github/rushyverse/api/player/Client.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/Client.kt @@ -1,10 +1,12 @@ package com.github.rushyverse.api.player -import com.github.rushyverse.api.API -import fr.mrmicky.fastboard.FastBoard +import com.github.rushyverse.api.APIPlugin import com.github.rushyverse.api.delegate.DelegatePlayer +import com.github.rushyverse.api.koin.inject import com.github.rushyverse.api.player.exception.PlayerNotFoundException +import com.github.rushyverse.api.player.scoreboard.ScoreboardManager import com.github.rushyverse.api.translation.SupportedLanguage +import fr.mrmicky.fastboard.FastBoard import kotlinx.coroutines.CoroutineScope import net.kyori.adventure.text.Component import net.kyori.adventure.text.Component.text @@ -19,16 +21,13 @@ import java.util.* public open class Client( pluginId: String, public val playerUUID: UUID, - coroutineScope: CoroutineScope + coroutineScope: CoroutineScope, + public var lang: SupportedLanguage = SupportedLanguage.ENGLISH ) : CoroutineScope by coroutineScope { - public val player: Player? by DelegatePlayer(pluginId, playerUUID) - - public val fastBoard: FastBoard by lazy { API.getOrInitFastBoard(requirePlayer()) } + private val scoreboardManager: ScoreboardManager by inject(APIPlugin.ID) - public var lang: SupportedLanguage = SupportedLanguage.ENGLISH - - public val locale: Locale get() = lang.locale + public val player: Player? by DelegatePlayer(pluginId, playerUUID) /** * Retrieve the instance of player. @@ -42,4 +41,11 @@ public open class Client( public fun send(message: String): Unit = send(text(message)) -} \ No newline at end of file + /** + * Retrieve the scoreboard of the player. + * The scoreboard will be created if it doesn't exist. + * @return The scoreboard of the player. + */ + public suspend fun scoreboard(): FastBoard = scoreboardManager.getOrCreate(requirePlayer()) + +} diff --git a/src/main/kotlin/com/github/rushyverse/api/player/PluginClientEvents.kt b/src/main/kotlin/com/github/rushyverse/api/player/PluginClientEvents.kt deleted file mode 100644 index f08577dc..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/player/PluginClientEvents.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.github.rushyverse.api.player - -import net.kyori.adventure.text.Component -import java.util.concurrent.atomic.AtomicReference - -/** - * Abstract class for handling client events individually for API dependant plugins. - * This approach is necessary to handle client events individually - * for each plugin depending on the API. - * - * The call event approach is therefore not favorable because - * it broadcasts the event for all dependent plugins currently running. - */ -public abstract class PluginClientEvents { - - /** - * Manage when the plugin creates a new client. - * @param client The ready-to-use client. - * @param joinMessage The message to display when creating this client, - * it is directly affiliated to the PlayerJoinEvent. - */ - public abstract suspend fun onJoin(client: Client, joinMessage: AtomicReference) - - /** - * Manage when the plugin removes a client. - * @param client The client being deleted. - * @param quitMessage The message to display when deleting this client, - * it is directly affiliated to the PlayerQuitEvent. - */ - public abstract suspend fun onQuit(client: Client, quitMessage: AtomicReference) -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/player/scoreboard/ScoreboardManager.kt b/src/main/kotlin/com/github/rushyverse/api/player/scoreboard/ScoreboardManager.kt new file mode 100644 index 00000000..52728cad --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/player/scoreboard/ScoreboardManager.kt @@ -0,0 +1,25 @@ +package com.github.rushyverse.api.player.scoreboard + +import fr.mrmicky.fastboard.FastBoard +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import org.bukkit.entity.Player + +public class ScoreboardManager { + + private val mutex = Mutex() + + private val _scoreboards = mutableMapOf() + + public val scoreboards: Map = _scoreboards + + public suspend fun getOrCreate(player: Player): FastBoard = mutex.withLock { + _scoreboards.getOrPut(player.name) { + FastBoard(player) + } + } + + public suspend fun remove(player: Player): FastBoard? = mutex.withLock { + _scoreboards.remove(player.name) + }?.also { it.delete() } +} diff --git a/src/main/kotlin/com/github/rushyverse/api/schedule/AbstractScheduler.kt b/src/main/kotlin/com/github/rushyverse/api/schedule/AbstractScheduler.kt index cda3a281..7c7b230b 100644 --- a/src/main/kotlin/com/github/rushyverse/api/schedule/AbstractScheduler.kt +++ b/src/main/kotlin/com/github/rushyverse/api/schedule/AbstractScheduler.kt @@ -18,10 +18,7 @@ public abstract class AbstractScheduler( get() = job?.isActive == true override fun start() { - if (running) { - throw IllegalStateException("The scheduling is already running") - } - + require(!running) { "The scheduling is already running" } job = coroutineScope.launch { run() } @@ -42,4 +39,4 @@ public abstract class AbstractScheduler( job?.cancelAndJoin() job = null } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/schedule/SchedulerTask.kt b/src/main/kotlin/com/github/rushyverse/api/schedule/SchedulerTask.kt index 2b75cf4c..0ed1e35f 100644 --- a/src/main/kotlin/com/github/rushyverse/api/schedule/SchedulerTask.kt +++ b/src/main/kotlin/com/github/rushyverse/api/schedule/SchedulerTask.kt @@ -15,10 +15,10 @@ private val log = KotlinLogging.logger { } /** * Allows executing code while the task is not cancelled. - * The [tasks] contains several body function that one will be executed after each [delay]. + * The [tasks] contains several body functions that one will be executed after each [delay]. * @property delay Time to wait between each execution. * @property _tasks Task executed in the coroutine context each time. - * @property tasks Immutable list of task executed in the coroutine context each time. + * @property tasks Immutable list of a task executed in the coroutine context each time. * @property mutex Mutex to synchronized data of the scheduler. * @property nextTaskIndex Next index of the task that will be executed. */ @@ -122,7 +122,7 @@ public class SchedulerTask( /** * Add a new body should be executed in the scheduler. * The operation is made with possible asynchronous effect if the scheduler is running. - * It's recommend to use this function when you are sure that the scheduler is not running. + * It's recommended to use this function when you are sure that the scheduler is not running. * @param id Id of the task created. * @param body Lambda function. * @return Task created. @@ -150,7 +150,7 @@ public class SchedulerTask( /** * Add a new body should be executed in the scheduler. * The operation is made with possible asynchronous effect if the scheduler is running. - * It's recommend to use this function when you are sure that the scheduler is not running. + * It's recommended to use this function when you are sure that the scheduler is not running. * @param index Index at which the specified element is to be inserted. * @param id Id of the task created. * @param body Lambda function. @@ -184,7 +184,7 @@ public class SchedulerTask( /** * Remove a task from the scheduler. * The operation is made with possible asynchronous effect if the scheduler is running. - * It's recommend to use this function when you are sure that the scheduler is not running. + * It's recommended to use this function when you are sure that the scheduler is not running. * @param id Id of the task registered into the scheduler. * @return `true` if a task has been removed, `false` otherwise. */ @@ -208,7 +208,7 @@ public class SchedulerTask( * Remove the task at the index. * Throws exception if no task is present at the index. * The operation is made with possible asynchronous effect if the scheduler is running. - * It's recommend to use this function when you are sure that the scheduler is not running. + * It's recommended to use this function when you are sure that the scheduler is not running. * If necessary, shift the next task to not skip one task. * @param index Index of the task. */ @@ -223,4 +223,4 @@ public class SchedulerTask( nextTaskIndex-- } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationsProvider.kt b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationsProvider.kt index ac32d59e..339133e2 100644 --- a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationsProvider.kt +++ b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationsProvider.kt @@ -12,7 +12,7 @@ public fun ResourceBundleTranslationsProvider.registerResourceBundleForSupported bundleName: String, loader: (String, Locale) -> ResourceBundle ) { - SupportedLanguage.values().forEach { + SupportedLanguage.entries.forEach { registerResourceBundle(bundleName, it.locale, loader) } } @@ -82,4 +82,4 @@ public open class ResourceBundleTranslationsProvider : TranslationsProvider() { * @return The key created. */ private fun createKey(bundleName: String, locale: Locale) = bundleName to locale -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt b/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt new file mode 100644 index 00000000..2e55456c --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt @@ -0,0 +1,40 @@ +package com.github.rushyverse.api.world + +import com.github.rushyverse.api.extension.centerRelative +import com.github.rushyverse.api.extension.minMaxOf +import org.bukkit.Location + +/** + * A cuboid area defined by two positions. + * @property min Minimum position. + * @property max Maximum position. + * @property location Center position of the cube. + */ +public class CubeArea( + loc1: Location, + loc2: Location +) { + + public var location: Location + get() = max.centerRelative(min) + set(value) { + // The new position becomes the center of the cube. + val halfSize = max.centerRelative(min) + min = value.subtract(halfSize) + max = value.add(halfSize) + } + + public var min: Location + private set + + public var max: Location + private set + + init { + val (x1, x2) = minMaxOf(loc1.x, loc2.x) + val (y1, y2) = minMaxOf(loc1.y, loc2.y) + val (z1, z2) = minMaxOf(loc1.z, loc2.z) + this.min = Location(loc1.world, x1, y1, z1) + this.max = Location(loc2.world, x2, y2, z2) + } +} diff --git a/src/main/kotlin/com/github/rushyverse/api/world/Cuboid.kt b/src/main/kotlin/com/github/rushyverse/api/world/Cuboid.kt deleted file mode 100644 index 004c4343..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/world/Cuboid.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.github.rushyverse.api.world - -import com.github.rushyverse.api.extension.getStringOrException -import org.bukkit.configuration.ConfigurationSection -import kotlin.jvm.internal.Intrinsics - -public class Cuboid( - public val pos1: Pos, - public val pos2: Pos -) { - - public operator fun contains(position: Pos): Boolean { - Intrinsics.checkNotNullParameter(position, "position") - val minX: Double = pos1.x.coerceAtMost(pos2.x) - val maxX: Double = pos1.x.coerceAtLeast(pos2.x) - val minY: Double = pos1.y.coerceAtMost(pos2.y) - val maxY: Double = pos1.y.coerceAtLeast(pos2.y) - val minZ: Double = pos1.z.coerceAtMost(pos2.z) - val maxZ: Double = pos1.z.coerceAtLeast(pos2.z) - val x: Double = position.x - if (x in minX..maxX) { - val y: Double = position.y - if (y in minY..maxY) { - val z: Double = position.z - if (z in minZ..maxZ) { - return true - } - } - } - return false - } - - public companion object { - public fun parse(section: ConfigurationSection): Cuboid = Cuboid( - Pos.parse(section.getStringOrException("pos1")), - Pos.parse(section.getStringOrException("pos2")) - ) - } -} diff --git a/src/main/kotlin/com/github/rushyverse/api/world/Pos.kt b/src/main/kotlin/com/github/rushyverse/api/world/Pos.kt deleted file mode 100644 index 9d2fa9bd..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/world/Pos.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.github.rushyverse.api.world - -import org.bukkit.Location -import org.bukkit.World - -public data class Pos( - val x: Double, - val y: Double, - val z: Double, - val yaw: Float = 0F, - val pitch: Float = 0F -) { - public companion object { - public fun parse(strPosition: String): Pos { - val split = strPosition.split(" ") - - val yaw = if (split.size > 3) { - split[3].toFloat() - } else 0F - - val pitch = if (split.size > 4) { - split[4].toFloat() - } else 0F - - return Pos( - split[0].toDouble(), - split[1].toDouble(), - split[2].toDouble(), - yaw, - pitch, - ) - } - } - - public fun toLocation(world: World): Location = Location(world, x, y, z, yaw, pitch) -} \ No newline at end of file From 17c77bb1d9d0dced1d337ccd435e8585af2d2019 Mon Sep 17 00:00:00 2001 From: Distractic Date: Wed, 26 Jul 2023 00:39:04 +0200 Subject: [PATCH 059/143] tests: Fix error with koin --- .../rushyverse/api/listener/PlayerListener.kt | 1 + .../api/player/scoreboard/ScoreboardManager.kt | 6 +++--- .../com/github/rushyverse/api/AbstractKoinTest.kt | 7 ++++++- .../rushyverse/api/listener/PlayerListenerTest.kt | 15 +++++++++++++-- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt index 94555df3..b8e400d6 100644 --- a/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt +++ b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt @@ -60,6 +60,7 @@ public class PlayerListener( @EventHandler(priority = EventPriority.HIGHEST) public suspend fun onQuit(event: PlayerQuitEvent) { val player = event.player + println(scoreboardManager) scoreboardManager.remove(player) val client = clients.removeClient(player) ?: return diff --git a/src/main/kotlin/com/github/rushyverse/api/player/scoreboard/ScoreboardManager.kt b/src/main/kotlin/com/github/rushyverse/api/player/scoreboard/ScoreboardManager.kt index 52728cad..0932e330 100644 --- a/src/main/kotlin/com/github/rushyverse/api/player/scoreboard/ScoreboardManager.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/scoreboard/ScoreboardManager.kt @@ -19,7 +19,7 @@ public class ScoreboardManager { } } - public suspend fun remove(player: Player): FastBoard? = mutex.withLock { - _scoreboards.remove(player.name) - }?.also { it.delete() } + public suspend fun remove(player: Player) { + mutex.withLock { _scoreboards.remove(player.name) }?.delete() + } } diff --git a/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt b/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt index e11100fa..429216a9 100644 --- a/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt @@ -23,6 +23,7 @@ abstract class AbstractKoinTest { open fun onBefore() { pluginId = getRandomString() CraftContext.startKoin(pluginId) { } + CraftContext.startKoin(APIPlugin.ID) { } loadTestModule { plugin = mockk(getRandomString()) @@ -40,6 +41,7 @@ abstract class AbstractKoinTest { @AfterTest open fun onAfter() { CraftContext.stopKoin(pluginId) + CraftContext.stopKoin(APIPlugin.ID) } inline fun testInject(): T = CraftContext.get(pluginId).inject().value @@ -47,4 +49,7 @@ abstract class AbstractKoinTest { fun loadTestModule(moduleDeclaration: ModuleDeclaration): Module = loadModule(pluginId, false, moduleDeclaration) -} \ No newline at end of file + fun loadApiTestModule(moduleDeclaration: ModuleDeclaration): Module = + loadModule(APIPlugin.ID, true, moduleDeclaration) + +} diff --git a/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt b/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt index d5b58fbf..18802836 100644 --- a/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt @@ -5,9 +5,9 @@ import com.github.rushyverse.api.player.Client import com.github.rushyverse.api.player.ClientManager import com.github.rushyverse.api.player.ClientManagerImpl import com.github.rushyverse.api.player.exception.ClientAlreadyExistsException +import com.github.rushyverse.api.player.scoreboard.ScoreboardManager import com.github.rushyverse.api.utils.getRandomString -import io.mockk.every -import io.mockk.mockk +import io.mockk.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -29,6 +29,8 @@ class PlayerListenerTest : AbstractKoinTest() { private lateinit var listener: PlayerListener + private lateinit var scoreboardManagerMock: ScoreboardManager + @BeforeTest override fun onBefore() { super.onBefore() @@ -36,6 +38,11 @@ class PlayerListenerTest : AbstractKoinTest() { loadTestModule { single { clientManager } } + + scoreboardManagerMock = mockk() + loadApiTestModule { + single { scoreboardManagerMock } + } listener = PlayerListener(plugin) } @@ -104,12 +111,16 @@ class PlayerListenerTest : AbstractKoinTest() { @Test fun `client linked to the player is removed and cancelled`() = runTest { val player = createPlayerMock() + coJustRun { scoreboardManagerMock.remove(any()) } + val client = Client(pluginId, player.uniqueId, CoroutineScope(Dispatchers.Main + SupervisorJob())) clientManager.put(player, client) + listener.onQuit(createEvent(player)) assertEquals(0, clientManager.clients.size) assertFalse { client.isActive } + coVerify { scoreboardManagerMock.remove(player) } } private fun createEvent(player: Player): PlayerQuitEvent { From 45c5bf93661692c3de632084aaec1b09b7bd359d Mon Sep 17 00:00:00 2001 From: Distractic Date: Wed, 26 Jul 2023 07:51:49 +0200 Subject: [PATCH 060/143] chore: Format --- README.md | 2 +- .../com/github/rushyverse/api/APIPlugin.kt | 1 + .../com/github/rushyverse/api/Plugin.kt | 14 ++---- .../api/extension/ConfigurationSectionExt.kt | 25 ---------- .../rushyverse/api/extension/_Comparable.kt | 4 +- .../api/extension/_CoroutineScope.kt | 14 +++--- .../rushyverse/api/extension/_ItemStack.kt | 6 +-- .../github/rushyverse/api/extension/_Math.kt | 2 +- .../api/extension/_MerchantRecipe.kt | 6 +-- .../rushyverse/api/extension/_Number.kt | 2 +- .../api/extension/_PersistentDataHolder.kt | 4 +- .../rushyverse/api/extension/_Player.kt | 8 +-- .../rushyverse/api/extension/_Runnable.kt | 4 +- .../rushyverse/api/extension/_String.kt | 6 +-- .../api/extension/event/_Cancellable.kt | 4 +- .../api/extension/{ => event}/_Event.kt | 4 +- .../api/extension/event/_Listener.kt | 6 +-- .../api/game/stats/KillableStats.kt | 4 +- .../rushyverse/api/game/team/TeamType.kt | 24 ++++----- .../rushyverse/api/item/CraftBuilder.kt | 6 +-- .../rushyverse/api/listener/PlayerListener.kt | 4 +- .../api/listener/VillagerListener.kt | 4 +- .../rushyverse/api/player/ClientManager.kt | 10 ++-- .../api/player/ClientManagerImpl.kt | 4 +- .../exception/ClientAlreadyExistsException.kt | 4 +- .../player/exception/PlayerQuitException.kt | 4 +- .../rushyverse/api/schedule/SchedulerTask.kt | 2 +- .../ResourceBundleNotRegisteredException.kt | 4 +- ...t => ResourceBundleTranslationProvider.kt} | 8 +-- ...ionsProvider.kt => TranslationProvider.kt} | 4 +- src/main/resources/api_translate.properties | 1 - .../resources/api_translate_fr_FR.properties | 3 +- .../github/rushyverse/api/AbstractKoinTest.kt | 12 ++--- .../api/extension/CommandSenderExtTest.kt | 11 ++-- .../rushyverse/api/extension/EventExtTest.kt | 3 +- .../api/extension/ItemStackExtTest.kt | 4 +- .../api/extension/JavaPluginExtTest.kt | 8 +-- .../api/extension/LocationExtTest.kt | 8 +-- .../api/extension/MerchantRecipeExtTest.kt | 6 +-- .../api/extension/PersistentDataHolderTest.kt | 4 +- .../rushyverse/api/extension/PlayerExtTest.kt | 31 ++++++------ .../api/extension/VillagerExtTest.kt | 10 ++-- .../rushyverse/api/extension/WorldExtTest.kt | 20 ++++---- .../rushyverse/api/koin/CraftContextTest.kt | 50 +++++++++---------- .../api/listener/PlayerListenerTest.kt | 4 +- .../api/listener/VillagerListenerTest.kt | 6 +-- .../api/player/ClientManagerImplTest.kt | 4 +- .../api/schedule/SchedulerTaskTest.kt | 28 +++++------ .../github/rushyverse/api/utils/Generator.kt | 8 +-- .../github/rushyverse/api/world/CuboidTest.kt | 6 --- 50 files changed, 191 insertions(+), 230 deletions(-) delete mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/ConfigurationSectionExt.kt rename src/main/kotlin/com/github/rushyverse/api/extension/{ => event}/_Event.kt (89%) rename src/main/kotlin/com/github/rushyverse/api/translation/{ResourceBundleTranslationsProvider.kt => ResourceBundleTranslationProvider.kt} (90%) rename src/main/kotlin/com/github/rushyverse/api/translation/{TranslationsProvider.kt => TranslationProvider.kt} (96%) delete mode 100644 src/test/kotlin/com/github/rushyverse/api/world/CuboidTest.kt diff --git a/README.md b/README.md index acb68073..1ba73a78 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # API -This project allows to create a PaperMC based server easily in Kotlin. It provides a lot of features to create a Minecraft +This project allows creating a PaperMC based server easily in Kotlin. It provides a lot of features to create a Minecraft server. ## Usage diff --git a/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt b/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt index 32c34e66..8dd90929 100644 --- a/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt +++ b/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt @@ -12,6 +12,7 @@ public class APIPlugin : JavaPlugin() { public companion object { public const val ID: String = "api" + public const val BUNDLE_API: String = "api_translate" } override fun onEnable() { diff --git a/src/main/kotlin/com/github/rushyverse/api/Plugin.kt b/src/main/kotlin/com/github/rushyverse/api/Plugin.kt index 8601a94a..678f104d 100644 --- a/src/main/kotlin/com/github/rushyverse/api/Plugin.kt +++ b/src/main/kotlin/com/github/rushyverse/api/Plugin.kt @@ -1,5 +1,6 @@ package com.github.rushyverse.api +import com.github.rushyverse.api.APIPlugin.Companion.BUNDLE_API import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin import com.github.rushyverse.api.extension.registerListener import com.github.rushyverse.api.koin.* @@ -19,10 +20,6 @@ public abstract class Plugin : SuspendingJavaPlugin() { public abstract val id: String - public companion object { - public const val BUNDLE_API: String = "api_translate" - } - override suspend fun onEnableAsync() { super.onEnableAsync() @@ -53,7 +50,7 @@ public abstract class Plugin : SuspendingJavaPlugin() { } /** - * Create a new instance of client. + * Create a new instance of a client. * @param player Player linked to the client. * @return C The instance of the client. */ @@ -61,11 +58,10 @@ public abstract class Plugin : SuspendingJavaPlugin() { /** * Create a translation provider to provide translations for the [supported languages][SupportedLanguage]. - * @param bundles Bundles to load. * @return New translation provider. */ - protected open suspend fun createTranslationsProvider(): ResourceBundleTranslationsProvider = - ResourceBundleTranslationsProvider().apply { - registerResourceBundleForSupportedLocales(Plugin.Companion.BUNDLE_API, ResourceBundle::getBundle) + protected open suspend fun createTranslationProvider(): ResourceBundleTranslationProvider = + ResourceBundleTranslationProvider().apply { + registerResourceBundleForSupportedLocales(BUNDLE_API, ResourceBundle::getBundle) } } diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/ConfigurationSectionExt.kt b/src/main/kotlin/com/github/rushyverse/api/extension/ConfigurationSectionExt.kt deleted file mode 100644 index b1a12308..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/extension/ConfigurationSectionExt.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.github.rushyverse.api.extension - -import org.bukkit.Material -import org.bukkit.configuration.ConfigurationSection - -private fun ConfigurationSection.sectionException(): Nothing = - throw NullPointerException("ConfigurationSection not found: $currentPath") - -private fun ConfigurationSection.fieldException(): Nothing = - throw NullPointerException("Configuration Field not found: $currentPath") - -public fun ConfigurationSection.getSectionOrException(sectionName: String): ConfigurationSection { - return getConfigurationSection(sectionName) - ?: sectionException() -} - -public fun ConfigurationSection.getIntOrException(fieldPath: String): Int = - getInt(fieldPath) ?: fieldException() - - -public fun ConfigurationSection.getStringOrException(fieldPath: String): String = - getString(fieldPath) ?: fieldException() - -public fun ConfigurationSection.getMaterialOrException(fieldPath: String): Material = - Material.getMaterial(getStringOrException(fieldPath)) ?: fieldException() \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Comparable.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Comparable.kt index 53c46c02..6b79d1be 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_Comparable.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Comparable.kt @@ -2,7 +2,7 @@ package com.github.rushyverse.api.extension /** * Get the lowest and highest value in a specific index. - * The lowest value is placed at the index 0 and highest at the index 1. + * The lowest value is placed at index 0 and highest at index 1. * * @param a First value. * @param b Second value. @@ -12,4 +12,4 @@ public fun > minMax(a: T, b: T): Pair = if (a <= b) { a to b } else { b to a -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt index 06120773..8932f268 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt @@ -1,9 +1,7 @@ package com.github.rushyverse.api.extension import com.github.rushyverse.api.schedule.SchedulerTask -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.withContext +import kotlinx.coroutines.* import kotlin.coroutines.CoroutineContext import kotlin.coroutines.coroutineContext import kotlin.time.Duration @@ -22,12 +20,12 @@ import kotlin.time.Duration * when it completes. Note that the result of `withContext` invocation is * dispatched into the original context in a cancellable way with a **prompt cancellation guarantee**, * which means that if the original [coroutineContext], in which `withContext` was invoked, - * is cancelled by the time its dispatcher starts to execute the code, + * is canceled by the time its dispatcher starts to execute the code, * it discards the result of `withContext` and throws [CancellationException]. * - * The cancellation behaviour described above is enabled if and only if the dispatcher is being changed. + * The cancellation behavior described above is enabled if and only if the dispatcher is being changed. * For example, when using `withContext(NonCancellable) { ... }` there is no change in dispatcher and - * this call will not be cancelled neither on entry to the block inside `withContext` nor on exit from it. + * this call will not be canceled neither on entry to the block inside `withContext` nor on exit from it. */ public suspend inline fun withScopeContext( scope: CoroutineScope, @@ -53,11 +51,11 @@ public fun CoroutineScope.scheduledTask( } /** - * Create a new scheduler without task. + * Create a new scheduler without a task. * The [CoroutineScope] for the scheduler is a children of the [CoroutineScope] receiver. * @receiver CoroutineScope to launch the task. * @param delay Time to wait between each execution. * @return The instance of the scheduler. */ public fun CoroutineScope.scheduler(delay: Duration): SchedulerTask = - SchedulerTask(this, delay) \ No newline at end of file + SchedulerTask(this, delay) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_ItemStack.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_ItemStack.kt index cdd172ca..70341495 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_ItemStack.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_ItemStack.kt @@ -182,7 +182,7 @@ public fun Array.serializeToBytes(): ByteArray { } /** - * Serialize a collection of item to an encoded String with Base64. + * Serialize a collection of items to an encoded String with Base64. * @receiver Items. * @return The String encoded with items serialized. */ @@ -191,7 +191,7 @@ public fun Collection.serializeToBase64(): String { } /** - * Serialize a collection of item to a byte array. + * Serialize a collection of items to a byte array. * @receiver Items. * @return Byte array of items serialized. */ @@ -266,4 +266,4 @@ public fun ByteArray.deserializeItemsToArray(): Array { return inputStream().buffered().use { Array(it.read()) { _ -> ItemStack.deserializeBytes(it.readNBytes(it.read())) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Math.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Math.kt index c04d4742..4f0cdef5 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_Math.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Math.kt @@ -2,7 +2,7 @@ package com.github.rushyverse.api.extension /** * Get the lowest and highest value in a specific index. - * The lowest value is placed at the index 0 and highest at the index 1. + * The lowest value is placed at index 0 and highest at index 1. * * @param a First value. * @param b Second value. diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_MerchantRecipe.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_MerchantRecipe.kt index 39aad712..b6f1bbec 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_MerchantRecipe.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_MerchantRecipe.kt @@ -10,7 +10,7 @@ import org.bukkit.inventory.MerchantRecipe * the amount of the first ingredient is scaled by the recipe's. * @param uses Number of times this trade has been used. * @param experienceReward Whether to reward experience to the player for the trade. - * @param villagerExperience Amount of experience the villager earns from this trade. + * @param villagerExperience The Amount of experience the villager earns from this trade. * @param priceMultiplier price multiplier, can never be below zero. * @param demand This value is periodically updated by the * villager that owns this merchant recipe based on how often the recipe has @@ -20,7 +20,7 @@ import org.bukkit.inventory.MerchantRecipe * this merchant recipe. It is based on the player's individual reputation with * the villager, and the player's currently active status effects (see * {@link PotionEffectType#HERO_OF_THE_VILLAGE}). The influence of the player's - * reputation on the special price is scaled by the recipe's. + * reputation on the special price is scaled by the recipe. * @param ignoreDiscounts Whether all discounts on this trade should be ignored. * @param ingredients List of ingredients necessary to obtain [result] item. * @return New instance of [MerchantRecipe][org.bukkit.inventory.MerchantRecipe]. @@ -48,4 +48,4 @@ public fun MerchantRecipe( ignoreDiscounts ).also { it.ingredients = ingredients -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Number.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Number.kt index e4c54cbd..ef7e35a2 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_Number.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Number.kt @@ -46,7 +46,7 @@ public fun Int.toRomanNumerals(): String { var remaining = this var i = 0 return buildString { - while(remaining > 0) { + while (remaining > 0) { val romanValue = ROMAN_VALUES[i] repeat(remaining / romanValue) { append(ROMAN_NUMERALS[i]) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_PersistentDataHolder.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_PersistentDataHolder.kt index fc400dcb..7d69b82c 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_PersistentDataHolder.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_PersistentDataHolder.kt @@ -9,9 +9,9 @@ import kotlin.contracts.contract * Open the data container and manage info in it. * @receiver PersistentDataHolder. * @param block Function to use data container. - * @return Type of the returns type. + * @return Type of the return type. */ public inline fun PersistentDataHolder.dataContainer(block: PersistentDataContainer.() -> T): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return persistentDataContainer.block() -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Player.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Player.kt index c86286b6..95fb56ef 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_Player.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Player.kt @@ -7,7 +7,7 @@ import kotlin.contracts.InvocationKind import kotlin.contracts.contract /** - * Allows to edit the profile of the player. + * Allows editing the profile of the player. * The method must be called into the server thread. */ public inline fun Player.editProfile(editor: PlayerProfile.() -> Unit) { @@ -19,7 +19,7 @@ public inline fun Player.editProfile(editor: PlayerProfile.() -> Unit) { * Check if the [item] is equals to the one of the item in player's hands. * @receiver Player. * @param item Item to compare the item in hands. - * @return `true` if present in one of hand, `false` otherwise. + * @return `true` if present in one of hands, `false` otherwise. */ @Suppress("NOTHING_TO_INLINE") public inline fun Player.itemInHand(item: ItemStack): Boolean = itemInHand { @@ -30,10 +30,10 @@ public inline fun Player.itemInHand(item: ItemStack): Boolean = itemInHand { * Use the lambda [finder] to check if a specific item is present in one of both hands. * @receiver Player. * @param finder Lambda method to compare item. - * @return `true` if present in one of hand, `false` otherwise. + * @return `true` if present in one of hands, `false` otherwise. */ public inline fun Player.itemInHand(finder: (ItemStack) -> Boolean): Boolean { contract { callsInPlace(finder, InvocationKind.AT_LEAST_ONCE) } val inventory = inventory return finder(inventory.itemInMainHand) || finder(inventory.itemInOffHand) -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Runnable.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Runnable.kt index 510b531d..f260b6b9 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_Runnable.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Runnable.kt @@ -8,7 +8,7 @@ import org.bukkit.plugin.Plugin import org.bukkit.scheduler.BukkitRunnable /** - * Create a new bukkit runnable with the body as the function sent in parameter. + * Create new bukkit runnable with the body as the function sent in parameter. * @param task Function that will be processed when the bukkit runnable will be run. * @return New instance of bukkit runnable. */ @@ -40,4 +40,4 @@ public suspend inline fun onPrimaryThread( public suspend inline fun onAsyncThread( plugin: Plugin, noinline block: suspend CoroutineScope.() -> T -): T = withContext(plugin.asyncDispatcher, block) \ No newline at end of file +): T = withContext(plugin.asyncDispatcher, block) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt index 3d21ffa8..861e22c4 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt @@ -13,7 +13,7 @@ import java.math.BigInteger import java.util.* /** - * Length of the value of UUID. + * Length of a UUID. */ public const val UUID_SIZE: Int = 36 @@ -69,7 +69,7 @@ public fun String.toUUIDStrictOrNull(): UUID? = try { /** * Creates a [UUID] from the string standard. * The string must have the strict format of UUID (with dashes). - * If the string cannot be converted to UUID, throws an exception. + * If the string cannot be converted to UUID, throw an exception. * @receiver String with UUID format. * @return The UUID instance equals to the string value. * @throws IllegalArgumentException Exception if the value is not a valid uuid. @@ -93,7 +93,7 @@ public fun String.toUUIDOrNull(): UUID? = try { /** * Creates a [UUID] from a string. * The string can have dashes or not. - * If the string cannot be converted to UUID, throws an exception. + * If the string cannot be converted to UUID, throw an exception. * @receiver String with UUID format. * @return The UUID instance equals to the string value. * @throws IllegalArgumentException Exception if the value is not a valid uuid. diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/event/_Cancellable.kt b/src/main/kotlin/com/github/rushyverse/api/extension/event/_Cancellable.kt index c3010bbe..9f593e97 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/event/_Cancellable.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/event/_Cancellable.kt @@ -12,8 +12,8 @@ public fun Cancellable.cancel() { /** * Extension function allowing to cancel the current process by method calling with a condition. */ -public fun T.cancelIf(condition: T.() -> Boolean) { +public inline fun T.cancelIf(condition: T.() -> Boolean) { if (condition()) { cancel() } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Event.kt b/src/main/kotlin/com/github/rushyverse/api/extension/event/_Event.kt similarity index 89% rename from src/main/kotlin/com/github/rushyverse/api/extension/_Event.kt rename to src/main/kotlin/com/github/rushyverse/api/extension/event/_Event.kt index 7acf0c83..f1f1e25a 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_Event.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/event/_Event.kt @@ -1,4 +1,4 @@ -package com.github.rushyverse.api.extension +package com.github.rushyverse.api.extension.event import org.bukkit.entity.Damageable import org.bukkit.event.entity.EntityDamageEvent @@ -14,4 +14,4 @@ public fun EntityDamageEvent.finalDamagedHealth(): Double? { } else { null } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/event/_Listener.kt b/src/main/kotlin/com/github/rushyverse/api/extension/event/_Listener.kt index 0259b0e5..cea3e3f9 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/event/_Listener.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/event/_Listener.kt @@ -24,7 +24,7 @@ public fun Listener.unregister(): Unit = HandlerList.unregisterAll(this) * The current coroutine is suspended. When the coroutine is cancellable or resume, unregister the listener. * @param plugin Java plugin to register the listener. * @param priority Priority to register this event at. - * @param ignoreCancelled Whether to pass cancelled events or not. + * @param ignoreCancelled Whether to pass canceled events or not. * @param block Function to treat the received event, returns `true` to valid the event and stop the listening, `false` otherwise. */ public suspend inline fun waitEvent( @@ -51,7 +51,7 @@ public suspend inline fun waitEvent( * The current coroutine is suspended. When the coroutine is cancellable or resume, unregister the listener. * @param plugin Java plugin to register the listener. * @param priority Priority to register this event at. - * @param ignoreCancelled Whether to pass cancelled events or not. + * @param ignoreCancelled Whether to pass canceled events or not. * @param block Function to treat the received event, returns `true` to valid the event and stop the listening, `false` otherwise. */ public suspend inline fun waitEvent( @@ -87,4 +87,4 @@ public suspend inline fun waitEvent( ignoreCancelled ) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/game/stats/KillableStats.kt b/src/main/kotlin/com/github/rushyverse/api/game/stats/KillableStats.kt index 17929d14..319349bd 100644 --- a/src/main/kotlin/com/github/rushyverse/api/game/stats/KillableStats.kt +++ b/src/main/kotlin/com/github/rushyverse/api/game/stats/KillableStats.kt @@ -4,7 +4,7 @@ public open class KillableStats( public var kills: Int = 0, public var deaths: Int = 0, -) : Stats { + ) : Stats { public override fun calculateScore(): Int { val score = kills - deaths @@ -12,4 +12,4 @@ public open class KillableStats( return 0 return score } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt b/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt index 5fa05f6b..4a2ccf23 100644 --- a/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt +++ b/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt @@ -1,8 +1,8 @@ package com.github.rushyverse.api.game.team -import com.github.rushyverse.api.Plugin.Companion.BUNDLE_API +import com.github.rushyverse.api.APIPlugin.Companion.BUNDLE_API import com.github.rushyverse.api.translation.SupportedLanguage -import com.github.rushyverse.api.translation.TranslationsProvider +import com.github.rushyverse.api.translation.TranslationProvider import net.kyori.adventure.text.Component import net.kyori.adventure.text.Component.text import java.util.* @@ -21,31 +21,31 @@ public enum class TeamType { ; public fun name( - translationsProvider: TranslationsProvider, + translationProvider: TranslationProvider, locale: Locale = SupportedLanguage.ENGLISH.locale - ): String = translationsProvider.translate("team.${name}", locale, BUNDLE_API) + ): String = translationProvider.translate("team.${name}", locale, BUNDLE_API) public fun textName( - translationsProvider: TranslationsProvider, + translationProvider: TranslationProvider, locale: Locale = SupportedLanguage.ENGLISH.locale - ): Component = text(name(translationsProvider, locale)) + ): Component = text(name(translationProvider, locale)) public fun memberAdjective( - translationsProvider: TranslationsProvider, + translationProvider: TranslationProvider, locale: Locale = SupportedLanguage.ENGLISH.locale ): String { val key = "team.${name}.member" - val result = translationsProvider.translate(key, locale, BUNDLE_API) + val result = translationProvider.translate(key, locale, BUNDLE_API) if (result == key) { - return name(translationsProvider, locale) + return name(translationProvider, locale) } return result } public fun textMemberAdjective( - translationsProvider: TranslationsProvider, + translationProvider: TranslationProvider, locale: Locale = SupportedLanguage.ENGLISH.locale - ): Component = text(memberAdjective(translationsProvider, locale)) -} \ No newline at end of file + ): Component = text(memberAdjective(translationProvider, locale)) +} diff --git a/src/main/kotlin/com/github/rushyverse/api/item/CraftBuilder.kt b/src/main/kotlin/com/github/rushyverse/api/item/CraftBuilder.kt index 3287a53e..1965d369 100644 --- a/src/main/kotlin/com/github/rushyverse/api/item/CraftBuilder.kt +++ b/src/main/kotlin/com/github/rushyverse/api/item/CraftBuilder.kt @@ -80,7 +80,7 @@ public class CraftBuilder { public var result: ItemStack? = null /** - * Define an item stack with the material as type at a position on the craft table. + * Define an item stack with the material as a type at a position on the craft table. * @param positions Positions of the item. * @param material Item type. * @return The item created from the material. @@ -126,7 +126,7 @@ public class CraftBuilder { /** * Define the value of the [result] property. - * An item is built from the builder and assign it as result of the craft. + * An item is built from the builder and assign it as a result of the craft. * @param builder Item builder. * @return The item built from the builder. */ @@ -209,4 +209,4 @@ public class CraftBuilder { (if (it == null) ' ' else itemKeys.getOrPut(it) { keyIncrement++ }) to it } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt index b8e400d6..0e7b3ceb 100644 --- a/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt +++ b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt @@ -39,7 +39,7 @@ public class PlayerListener( } /** - * Create a new instance of client and store it into [clients]. + * Create a new instance of a client and store it into [clients]. * If a client is already found for the player, throw an exception. * @param player Player linked to the client. * @return The instance of the client. @@ -54,7 +54,7 @@ public class PlayerListener( /** * Handle the quit event to remove the client linked to the player leaving. - * The life cycle of the client will be cancelled. + * The life cycle of the client will be canceled. * @param event Event. */ @EventHandler(priority = EventPriority.HIGHEST) diff --git a/src/main/kotlin/com/github/rushyverse/api/listener/VillagerListener.kt b/src/main/kotlin/com/github/rushyverse/api/listener/VillagerListener.kt index 0e23d791..f5d659cf 100644 --- a/src/main/kotlin/com/github/rushyverse/api/listener/VillagerListener.kt +++ b/src/main/kotlin/com/github/rushyverse/api/listener/VillagerListener.kt @@ -15,11 +15,11 @@ public class VillagerListener(private val plugin: JavaPlugin) : Listener { /** * When a villager will have his profession changed, check if a specific tag is present into the entity. - * If the tag is present, the event will be cancelled, otherwise the career will be changed. + * If the tag is present, the event will be canceled, otherwise the career will be changed. * @param event Event when a villager will lose his job. */ @EventHandler public fun onChangeCareer(event: VillagerCareerChangeEvent) { event.isCancelled = event.entity.keepProfession(plugin) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/player/ClientManager.kt b/src/main/kotlin/com/github/rushyverse/api/player/ClientManager.kt index ef8bfb33..0c88e8a6 100644 --- a/src/main/kotlin/com/github/rushyverse/api/player/ClientManager.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/ClientManager.kt @@ -34,7 +34,7 @@ public suspend inline fun ClientManager.getTypedClientOrNul /** * Manage the existing client present in the server. - * @property clients Synchronized mutable map of client as value and name of player as key. + * @property clients Synchronized mutable map of clients as value and name of player as a key. */ public interface ClientManager { @@ -43,21 +43,21 @@ public interface ClientManager { /** * Put a new client in the server. * @param client New client added. - * @return The previous value associated with key, or null there is none. + * @return The previous value associated with a key, or null there is none. */ public suspend fun put(player: Player, client: Client): Client? /** * Put a new client in the server if no client is linked to the player. * @param client New client added. - * @return The previous value associated with key, or null there is none. + * @return The previous value associated with a key, or null there is none. */ public suspend fun putIfAbsent(player: Player, client: Client): Client? /** * Remove a client from the server by a Player. * @param player Player linked to a Client. - * @return The client that was removed, null otherwise. + * @return The client that was removed null otherwise. */ public suspend fun removeClient(player: Player): Client? @@ -95,4 +95,4 @@ public interface ClientManager { * @return `true` if there is a client for the player, `false` otherwise. */ public suspend fun contains(player: Player): Boolean -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/player/ClientManagerImpl.kt b/src/main/kotlin/com/github/rushyverse/api/player/ClientManagerImpl.kt index f2e44c7b..b43c02f4 100644 --- a/src/main/kotlin/com/github/rushyverse/api/player/ClientManagerImpl.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/ClientManagerImpl.kt @@ -8,7 +8,7 @@ import org.bukkit.entity.Player /** * Manage the existing client present in the server. * The clients are stored with the name of the player. - * @property _clients Synchronized mutable map of client as value and name of player as key. + * @property _clients Synchronized mutable map of clients as value and name of player as a key. */ public class ClientManagerImpl : ClientManager { @@ -66,4 +66,4 @@ public class ClientManagerImpl : ClientManager { _clients.containsKey(getKey(player)) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/player/exception/ClientAlreadyExistsException.kt b/src/main/kotlin/com/github/rushyverse/api/player/exception/ClientAlreadyExistsException.kt index ea5eba74..08c10d50 100644 --- a/src/main/kotlin/com/github/rushyverse/api/player/exception/ClientAlreadyExistsException.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/exception/ClientAlreadyExistsException.kt @@ -1,6 +1,6 @@ package com.github.rushyverse.api.player.exception /** - * Exception if a client is already exists for a player. + * Exception if a client already exists for a player. */ -public class ClientAlreadyExistsException(message: String? = null) : RuntimeException(message) \ No newline at end of file +public class ClientAlreadyExistsException(message: String? = null) : RuntimeException(message) diff --git a/src/main/kotlin/com/github/rushyverse/api/player/exception/PlayerQuitException.kt b/src/main/kotlin/com/github/rushyverse/api/player/exception/PlayerQuitException.kt index 92a6774d..ba47291b 100644 --- a/src/main/kotlin/com/github/rushyverse/api/player/exception/PlayerQuitException.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/exception/PlayerQuitException.kt @@ -4,6 +4,6 @@ import kotlin.coroutines.cancellation.CancellationException /** * Exception throw when the player quits the server. - * Allows canceling all coroutine linked to a player. + * Allows canceling all coroutines linked to a player. */ -public class PlayerQuitException(message: String? = null) : CancellationException(message) \ No newline at end of file +public class PlayerQuitException(message: String? = null) : CancellationException(message) diff --git a/src/main/kotlin/com/github/rushyverse/api/schedule/SchedulerTask.kt b/src/main/kotlin/com/github/rushyverse/api/schedule/SchedulerTask.kt index 0ed1e35f..3fea81fa 100644 --- a/src/main/kotlin/com/github/rushyverse/api/schedule/SchedulerTask.kt +++ b/src/main/kotlin/com/github/rushyverse/api/schedule/SchedulerTask.kt @@ -14,7 +14,7 @@ import kotlin.time.Duration private val log = KotlinLogging.logger { } /** - * Allows executing code while the task is not cancelled. + * Allows executing code while the task is not canceled. * The [tasks] contains several body functions that one will be executed after each [delay]. * @property delay Time to wait between each execution. * @property _tasks Task executed in the coroutine context each time. diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleNotRegisteredException.kt b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleNotRegisteredException.kt index 5fc5442a..93761db1 100644 --- a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleNotRegisteredException.kt +++ b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleNotRegisteredException.kt @@ -3,9 +3,9 @@ package com.github.rushyverse.api.translation import java.util.* /** - * Exception used when the system try to use a resource bundle not registered. + * Exception used when the system tries to use a resource bundle not registered. * @param bundleName Name of the bundle. * @param locale Locale searched. */ public class ResourceBundleNotRegisteredException(public val bundleName: String, public val locale: Locale) : - RuntimeException("The bundle [$bundleName] for locale [$locale] is not registered.") \ No newline at end of file + RuntimeException("The bundle [$bundleName] for locale [$locale] is not registered.") diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationsProvider.kt b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProvider.kt similarity index 90% rename from src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationsProvider.kt rename to src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProvider.kt index 339133e2..b7c9db0b 100644 --- a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationsProvider.kt +++ b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProvider.kt @@ -6,9 +6,9 @@ import java.util.* /** * Loads the [ResourceBundle] called [bundleName] for all supported locales from [SupportedLanguage]. - * @see ResourceBundleTranslationsProvider.registerResourceBundle + * @see ResourceBundleTranslationProvider.registerResourceBundle */ -public fun ResourceBundleTranslationsProvider.registerResourceBundleForSupportedLocales( +public fun ResourceBundleTranslationProvider.registerResourceBundleForSupportedLocales( bundleName: String, loader: (String, Locale) -> ResourceBundle ) { @@ -23,7 +23,7 @@ private val logger = KotlinLogging.logger { } * Translation provider backed by Java's [ResourceBundle]s. This makes use of `.properties` files that are standard * across the Java ecosystem. */ -public open class ResourceBundleTranslationsProvider : TranslationsProvider() { +public open class ResourceBundleTranslationProvider : TranslationProvider() { private val bundles: MutableMap, ResourceBundle> = mutableMapOf() @@ -76,7 +76,7 @@ public open class ResourceBundleTranslationsProvider : TranslationsProvider() { } /** - * Create the key to retrieve bundle. + * Create the key to retrieve a bundle. * @param bundleName Name of the bundle. * @param locale Locale. * @return The key created. diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/TranslationsProvider.kt b/src/main/kotlin/com/github/rushyverse/api/translation/TranslationProvider.kt similarity index 96% rename from src/main/kotlin/com/github/rushyverse/api/translation/TranslationsProvider.kt rename to src/main/kotlin/com/github/rushyverse/api/translation/TranslationProvider.kt index 82c81bb9..4e6e4c95 100644 --- a/src/main/kotlin/com/github/rushyverse/api/translation/TranslationsProvider.kt +++ b/src/main/kotlin/com/github/rushyverse/api/translation/TranslationProvider.kt @@ -5,7 +5,7 @@ import java.util.* /** * Translation provider interface, in charge of taking string keys and returning translated strings. */ -public abstract class TranslationsProvider { +public abstract class TranslationProvider { /** * Get a translation by key from the given locale and bundle name. @@ -40,4 +40,4 @@ public abstract class TranslationsProvider { bundleName: String, replacements: Collection ): String = translate(key, locale, bundleName, replacements.toTypedArray()) -} \ No newline at end of file +} diff --git a/src/main/resources/api_translate.properties b/src/main/resources/api_translate.properties index 8cf5a7c9..da735fea 100644 --- a/src/main/resources/api_translate.properties +++ b/src/main/resources/api_translate.properties @@ -6,5 +6,4 @@ team.yellow=Yellow team.purple=Purple team.aqua=Aqua team.black=Black - player.join.team={0} joined {1} team diff --git a/src/main/resources/api_translate_fr_FR.properties b/src/main/resources/api_translate_fr_FR.properties index 341b85a8..c3784d13 100644 --- a/src/main/resources/api_translate_fr_FR.properties +++ b/src/main/resources/api_translate_fr_FR.properties @@ -10,5 +10,4 @@ team.purple=Violette team.purple.member=Violet team.aqua=Aqua team.black=Noire - -player.join.team={0} a rejoint l'quipe {1} \ No newline at end of file +player.join.team={0} a rejoint l'quipe {1} diff --git a/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt b/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt index 429216a9..642837aa 100644 --- a/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt @@ -2,7 +2,7 @@ package com.github.rushyverse.api import com.github.rushyverse.api.koin.CraftContext import com.github.rushyverse.api.koin.loadModule -import com.github.rushyverse.api.utils.getRandomString +import com.github.rushyverse.api.utils.randomString import io.mockk.every import io.mockk.mockk import org.bukkit.Server @@ -13,7 +13,7 @@ import kotlin.test.BeforeTest abstract class AbstractKoinTest { - lateinit var plugin: com.github.rushyverse.api.Plugin + lateinit var plugin: Plugin lateinit var server: Server @@ -21,17 +21,17 @@ abstract class AbstractKoinTest { @BeforeTest open fun onBefore() { - pluginId = getRandomString() + pluginId = randomString() CraftContext.startKoin(pluginId) { } CraftContext.startKoin(APIPlugin.ID) { } loadTestModule { - plugin = mockk(getRandomString()) - server = mockk(getRandomString()) + plugin = mockk(randomString()) + server = mockk(randomString()) every { plugin.server } returns server every { plugin.id } returns pluginId - every { plugin.name } returns getRandomString() + every { plugin.name } returns randomString() single { plugin } single { server } diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/CommandSenderExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/CommandSenderExtTest.kt index c177ffa2..2da98ad2 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/CommandSenderExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/CommandSenderExtTest.kt @@ -1,6 +1,6 @@ package com.github.rushyverse.api.extension -import com.github.rushyverse.api.utils.getRandomString +import com.github.rushyverse.api.utils.randomString import io.mockk.justRun import io.mockk.mockk import io.mockk.slot @@ -22,16 +22,11 @@ class CommandSenderExtTest { val slotComponent = slot() justRun { sender.sendMessage(capture(slotComponent)) } - val content = getRandomString() + val content = randomString() sender.sendMessageError(content) val component = slotComponent.captured assertEquals(NamedTextColor.RED, component.color()) assertEquals(content, component.content()) } } - - @Nested - inner class Permissions { - - } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/EventExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/EventExtTest.kt index 027f341d..2efdd377 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/EventExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/EventExtTest.kt @@ -1,5 +1,6 @@ package com.github.rushyverse.api.extension +import com.github.rushyverse.api.extension.event.finalDamagedHealth import io.mockk.every import io.mockk.mockk import org.bukkit.entity.Damageable @@ -47,4 +48,4 @@ class EventExtTest { } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/ItemStackExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/ItemStackExtTest.kt index f2c8a114..afee343b 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/ItemStackExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/ItemStackExtTest.kt @@ -1,6 +1,6 @@ package com.github.rushyverse.api.extension -import com.github.rushyverse.api.utils.getRandomString +import com.github.rushyverse.api.utils.randomString import io.mockk.every import io.mockk.mockk import org.bukkit.Material @@ -319,7 +319,7 @@ class ItemStackExtTest { } private fun mockItem(material: Material): ItemStack { - return mockk(getRandomString()).apply { + return mockk(randomString()).apply { every { type } returns material } } diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/JavaPluginExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/JavaPluginExtTest.kt index 103e0f86..f3813639 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/JavaPluginExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/JavaPluginExtTest.kt @@ -2,7 +2,7 @@ package com.github.rushyverse.api.extension import com.github.rushyverse.api.item.CraftSlot import com.github.rushyverse.api.item.exception.CraftResultMissingException -import com.github.rushyverse.api.utils.getRandomString +import com.github.rushyverse.api.utils.randomString import io.mockk.every import io.mockk.mockk import io.mockk.slot @@ -32,7 +32,7 @@ class JavaPluginExtTest { server = mockk() every { plugin.server } returns server - every { plugin.name } returns getRandomString() + every { plugin.name } returns randomString() every { server.pluginManager } returns mockk() } @@ -109,7 +109,7 @@ class JavaPluginExtTest { every { server.addRecipe(capture(slotRecipe)) } returns true plugin.registerCraft { - for (index in CraftSlot.values()) { + for (index in CraftSlot.entries) { set(index, item = mockk()) } @@ -131,7 +131,7 @@ class JavaPluginExtTest { plugin.registerCraft { val item = mockk() - for (index in CraftSlot.values()) { + for (index in CraftSlot.entries) { set(index, item = item) } result = mockk().also { diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/LocationExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/LocationExtTest.kt index 6a35bfbb..a2e74020 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/LocationExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/LocationExtTest.kt @@ -1,6 +1,6 @@ package com.github.rushyverse.api.extension -import com.github.rushyverse.api.utils.getRandomString +import com.github.rushyverse.api.utils.randomString import io.mockk.mockk import org.bukkit.Location import org.bukkit.World @@ -17,7 +17,7 @@ class LocationExtTest { @BeforeTest fun onBefore() { - loc = Location(mockk(getRandomString()), 0.0, 1.0, 2.0, 3.0f, 4.0f) + loc = Location(mockk(randomString()), 0.0, 1.0, 2.0, 3.0f, 4.0f) } @Test @@ -93,7 +93,7 @@ class LocationExtTest { @Test fun `copy with only world property will change only the property`() { - val world = mockk(getRandomString()) + val world = mockk(randomString()) assertEquals(Location(world, loc.x, loc.y, loc.z, loc.yaw, loc.pitch), loc.copy(world = world)) } @@ -129,7 +129,7 @@ class LocationExtTest { @Test fun `copy with all args will change all properties`() { - val world = mockk(getRandomString()) + val world = mockk(randomString()) val x = loc.x + 10 val y = loc.y + 20 val z = loc.z + 30 diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/MerchantRecipeExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/MerchantRecipeExtTest.kt index d1513c02..ce953f01 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/MerchantRecipeExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/MerchantRecipeExtTest.kt @@ -1,6 +1,6 @@ package com.github.rushyverse.api.extension -import com.github.rushyverse.api.utils.getRandomString +import com.github.rushyverse.api.utils.randomString import io.mockk.mockk import org.bukkit.Material import org.bukkit.inventory.ItemStack @@ -13,7 +13,7 @@ class MerchantRecipeExtTest { @Test fun `constructor utils function`() { - val result = mockk(getRandomString()) + val result = mockk(randomString()) val maxUses = 1 val uses = 2 val experienceReward = false @@ -65,7 +65,7 @@ class MerchantRecipeExtTest { @Test fun `default constructor utils function`() { - val result = mockk(getRandomString()) + val result = mockk(randomString()) val maxUses = 10 val recipe = MerchantRecipe( result = result, diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/PersistentDataHolderTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/PersistentDataHolderTest.kt index 44557e22..9c948bee 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/PersistentDataHolderTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/PersistentDataHolderTest.kt @@ -1,6 +1,6 @@ package com.github.rushyverse.api.extension -import com.github.rushyverse.api.utils.getRandomString +import com.github.rushyverse.api.utils.randomString import io.mockk.mockk import org.bukkit.persistence.PersistentDataContainer import org.bukkit.persistence.PersistentDataHolder @@ -11,7 +11,7 @@ class PersistentDataHolderTest { @Test fun `open data container and manage it`() { - val container = mockk(getRandomString()) + val container = mockk(randomString()) val holder = PersistentDataHolder { container } val isEquals = holder.dataContainer { diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/PlayerExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/PlayerExtTest.kt index f11a7ca4..2cd81712 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/PlayerExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/PlayerExtTest.kt @@ -1,7 +1,8 @@ package com.github.rushyverse.api.extension import com.destroystokyo.paper.profile.PlayerProfile -import com.github.rushyverse.api.utils.getRandomString +import com.github.rushyverse.api.utils.randomBoolean +import com.github.rushyverse.api.utils.randomString import io.mockk.every import io.mockk.justRun import io.mockk.mockk @@ -20,23 +21,23 @@ class PlayerExtTest { @Test fun `edit profile use the current profile and redefine it with the same modified instance`() { - val player = mockk(getRandomString()) - val profile = mockk(getRandomString()) + val player = mockk(randomString()) + val profile = mockk(randomString()) every { player.playerProfile } returns profile - val slotName = slot() - every { profile.setName(capture(slotName)) } returns "" + val slot = slot() + every { profile.complete(capture(slot)) } returns randomBoolean() val slotProfile = slot() justRun { player.playerProfile = capture(slotProfile) } - val expectedName = getRandomString() + val expectedValue = randomBoolean() player.editProfile { - setName(expectedName) + complete(expectedValue) } assertEquals(profile, slotProfile.captured) - assertEquals(expectedName, slotName.captured) + assertEquals(expectedValue, slot.captured) } } @@ -48,20 +49,20 @@ class PlayerExtTest { @BeforeTest fun onBefore() { - player = mockk(getRandomString()) + player = mockk(randomString()) val inventory = mockk() every { player.inventory } returns inventory } @Test fun `compare with equals and item found`() { - val expectedItem = mockk(getRandomString()) + val expectedItem = mockk(randomString()) every { inventory.itemInMainHand } returns expectedItem - every { inventory.itemInOffHand } returns mockk(getRandomString()) + every { inventory.itemInOffHand } returns mockk(randomString()) assertTrue { player.itemInHand(expectedItem) } - every { inventory.itemInMainHand } returns mockk(getRandomString()) + every { inventory.itemInMainHand } returns mockk(randomString()) every { inventory.itemInOffHand } returns expectedItem assertTrue { player.itemInHand(expectedItem) } @@ -69,9 +70,9 @@ class PlayerExtTest { @Test fun `compare with equals and item not found`() { - val expectedItem = mockk(getRandomString()) - every { inventory.itemInMainHand } returns mockk(getRandomString()) - every { inventory.itemInOffHand } returns mockk(getRandomString()) + val expectedItem = mockk(randomString()) + every { inventory.itemInMainHand } returns mockk(randomString()) + every { inventory.itemInOffHand } returns mockk(randomString()) assertFalse { player.itemInHand(expectedItem) } } diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/VillagerExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/VillagerExtTest.kt index 5541e5b4..3d014a46 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/VillagerExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/VillagerExtTest.kt @@ -1,6 +1,6 @@ package com.github.rushyverse.api.extension -import com.github.rushyverse.api.utils.getRandomString +import com.github.rushyverse.api.utils.randomString import io.mockk.every import io.mockk.justRun import io.mockk.mockk @@ -29,7 +29,7 @@ class VillagerExtTest { @Test fun `returns true when data is present`() { - val villager = mockk(getRandomString()) + val villager = mockk(randomString()) val container = mockk() val slotNamespaced = slot() @@ -41,7 +41,7 @@ class VillagerExtTest { @Test fun `returns false when data is not present`() { - val villager = mockk(getRandomString()) + val villager = mockk(randomString()) val container = mockk() val slotNamespaced = slot() @@ -59,7 +59,7 @@ class VillagerExtTest { @Test fun `when true, set key into the data container`() { - val villager = mockk(getRandomString()) + val villager = mockk(randomString()) val container = mockk() val slotNamespaced = slot() @@ -72,7 +72,7 @@ class VillagerExtTest { @Test fun `when false, remove key into the data container`() { - val villager = mockk(getRandomString()) + val villager = mockk(randomString()) val container = mockk() val slotNamespaced = slot() diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/WorldExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/WorldExtTest.kt index f93c14d9..98d738c1 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/WorldExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/WorldExtTest.kt @@ -1,7 +1,7 @@ package com.github.rushyverse.api.extension -import com.github.rushyverse.api.utils.createRandomLocation -import com.github.rushyverse.api.utils.getRandomString +import com.github.rushyverse.api.utils.randomLocation +import com.github.rushyverse.api.utils.randomString import io.mockk.every import io.mockk.mockk import io.mockk.slot @@ -20,16 +20,16 @@ class WorldExtTest { @Test fun `await chunk at with block`() = runBlocking { - val world = mockk(getRandomString()) + val world = mockk(randomString()) val slotBlock = slot() val slotGen = slot() - val chunk = mockk(getRandomString()) + val chunk = mockk(randomString()) every { world.getChunkAtAsync(capture(slotBlock), capture(slotGen)) } returns CompletableFuture.completedFuture( chunk ) - val block = mockk(getRandomString()) + val block = mockk(randomString()) assertEquals(chunk, world.awaitChunkAt(block, true)) assertEquals(block, slotBlock.captured) assertTrue { slotGen.captured } @@ -39,16 +39,16 @@ class WorldExtTest { @Test fun `await chunk at with location`() = runBlocking { - val world = mockk(getRandomString()) + val world = mockk(randomString()) val slotLoc = slot() val slotGen = slot() - val chunk = mockk(getRandomString()) + val chunk = mockk(randomString()) every { world.getChunkAtAsync(capture(slotLoc), capture(slotGen)) } returns CompletableFuture.completedFuture( chunk ) - val location = createRandomLocation() + val location = randomLocation() assertEquals(chunk, world.awaitChunkAt(location, true)) assertEquals(location, slotLoc.captured) assertTrue { slotGen.captured } @@ -58,13 +58,13 @@ class WorldExtTest { @Test fun `await chunk at with coord`() = runBlocking { - val world = mockk(getRandomString()) + val world = mockk(randomString()) val slotX = slot() val slotZ = slot() val slotGen = slot() val slotUrgent = slot() - val chunk = mockk(getRandomString()) + val chunk = mockk(randomString()) every { world.getChunkAtAsync( capture(slotX), diff --git a/src/test/kotlin/com/github/rushyverse/api/koin/CraftContextTest.kt b/src/test/kotlin/com/github/rushyverse/api/koin/CraftContextTest.kt index 463fd5b8..34dfa3a1 100644 --- a/src/test/kotlin/com/github/rushyverse/api/koin/CraftContextTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/koin/CraftContextTest.kt @@ -1,6 +1,6 @@ package com.github.rushyverse.api.koin -import com.github.rushyverse.api.utils.getRandomString +import com.github.rushyverse.api.utils.randomString import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @@ -28,13 +28,13 @@ class CraftContextTest { @Test fun `when no instance exists for the id`() { assertThrows { - CraftContext.get(getRandomString()) + CraftContext.get(randomString()) } } @Test fun `when instance is registered for the id`() { - val id = getRandomString() + val id = randomString() CraftContext.startKoin(id) val koin = CraftContext.get(id) assertNotNull(koin) @@ -47,12 +47,12 @@ class CraftContextTest { @Test fun `when no instance exists for the id`() { - assertNull(CraftContext.getOrNull(getRandomString())) + assertNull(CraftContext.getOrNull(randomString())) } @Test fun `when instance is registered for the id`() { - val id = getRandomString() + val id = randomString() CraftContext.startKoin(id) val koin = CraftContext.get(id) assertNotNull(koin) @@ -65,13 +65,13 @@ class CraftContextTest { @Test fun `when no instance exists for the id`() { - CraftContext.stopKoin(getRandomString()) + CraftContext.stopKoin(randomString()) } @Test fun `when instance is registered for the id`() { assertEquals(0, CraftContext.koins.size) - val id = getRandomString() + val id = randomString() CraftContext.startKoin(id) val module = module { single { 1.2 } @@ -98,14 +98,14 @@ class CraftContextTest { @Test fun `when no instance exists for the id`() { assertEquals(0, CraftContext.koins.size) - CraftContext.startKoin(getRandomString()) {} + CraftContext.startKoin(randomString()) {} assertEquals(1, CraftContext.koins.size) } @Test fun `when instance already exists for the id`() { assertEquals(0, CraftContext.koins.size) - val id = getRandomString() + val id = randomString() CraftContext.startKoin(id) {} assertEquals(1, CraftContext.koins.size) assertThrows { @@ -117,7 +117,7 @@ class CraftContextTest { @Test fun `define module in declaration during starting`() { assertEquals(0, CraftContext.koins.size) - val id = getRandomString() + val id = randomString() CraftContext.startKoin(id) { this.modules(module { single { "hello" } @@ -137,14 +137,14 @@ class CraftContextTest { fun `when no instance exists for the id`() { assertEquals(0, CraftContext.koins.size) val koinApplication = KoinApplication.init() - CraftContext.startKoin(getRandomString(), koinApplication) + CraftContext.startKoin(randomString(), koinApplication) assertEquals(1, CraftContext.koins.size) } @Test fun `when instance already exists for the id`() { assertEquals(0, CraftContext.koins.size) - val id = getRandomString() + val id = randomString() val koinApplication = KoinApplication.init() CraftContext.startKoin(id, koinApplication) assertEquals(1, CraftContext.koins.size) @@ -161,7 +161,7 @@ class CraftContextTest { koinApplication.modules(module { single { "hello" } }) - val id = getRandomString() + val id = randomString() CraftContext.startKoin(id, koinApplication) val injectedString = CraftContext.get(id).get() assertEquals("hello", injectedString) @@ -176,13 +176,13 @@ class CraftContextTest { @Test fun `when no instance exists for the id`() { assertThrows { - CraftContext.loadKoinModules(getRandomString(), module { }) + CraftContext.loadKoinModules(randomString(), module { }) } } @Test fun `when instance exists for the id`() { - val id = getRandomString() + val id = randomString() CraftContext.startKoin(id) CraftContext.loadKoinModules(id, module { single { "hello" } @@ -198,13 +198,13 @@ class CraftContextTest { @Test fun `when no instance exists for the id`() { assertThrows { - CraftContext.loadKoinModules(getRandomString(), emptyList()) + CraftContext.loadKoinModules(randomString(), emptyList()) } } @Test fun `when instance exists for the id`() { - val id = getRandomString() + val id = randomString() CraftContext.startKoin(id) CraftContext.loadKoinModules(id, listOf( module { @@ -225,13 +225,13 @@ class CraftContextTest { @Test fun `when no instance exists for the id`() { assertThrows { - CraftContext.unloadKoinModules(getRandomString(), module { }) + CraftContext.unloadKoinModules(randomString(), module { }) } } @Test fun `when instance exists for the id but not module linked`() { - val id = getRandomString() + val id = randomString() CraftContext.startKoin(id) CraftContext.unloadKoinModules(id, module { single { "hello" } @@ -240,7 +240,7 @@ class CraftContextTest { @Test fun `when instance exists for the id`() { - val id = getRandomString() + val id = randomString() CraftContext.startKoin(id) val module = module { single { "hello" } @@ -262,13 +262,13 @@ class CraftContextTest { @Test fun `when no instance exists for the id`() { assertThrows { - CraftContext.unloadKoinModules(getRandomString(), emptyList()) + CraftContext.unloadKoinModules(randomString(), emptyList()) } } @Test fun `when instance exists for the id but not module linked`() { - val id = getRandomString() + val id = randomString() CraftContext.startKoin(id) CraftContext.unloadKoinModules(id, listOf(module { single { "hello" } @@ -277,7 +277,7 @@ class CraftContextTest { @Test fun `when instance exists for the id`() { - val id = getRandomString() + val id = randomString() CraftContext.startKoin(id) val module1 = module { single { "hello" } @@ -318,13 +318,13 @@ class CraftContextTest { @Test fun `when no instance exists for the id`() { assertThrows { - loadModule(getRandomString()) {} + loadModule(randomString()) {} } } @Test fun `when instance exists for the id with lazy module creation`() { - val id = getRandomString() + val id = randomString() CraftContext.startKoin(id) var isInit = false loadModule(id) { diff --git a/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt b/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt index 18802836..634abc65 100644 --- a/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt @@ -6,7 +6,7 @@ import com.github.rushyverse.api.player.ClientManager import com.github.rushyverse.api.player.ClientManagerImpl import com.github.rushyverse.api.player.exception.ClientAlreadyExistsException import com.github.rushyverse.api.player.scoreboard.ScoreboardManager -import com.github.rushyverse.api.utils.getRandomString +import com.github.rushyverse.api.utils.randomString import io.mockk.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -130,7 +130,7 @@ class PlayerListenerTest : AbstractKoinTest() { } private fun createPlayerMock(): Player { - val name = getRandomString() + val name = randomString() val player = mockk(name) every { player.name } returns name every { player.uniqueId } returns UUID.randomUUID() diff --git a/src/test/kotlin/com/github/rushyverse/api/listener/VillagerListenerTest.kt b/src/test/kotlin/com/github/rushyverse/api/listener/VillagerListenerTest.kt index 15fe4fcf..71cb693a 100644 --- a/src/test/kotlin/com/github/rushyverse/api/listener/VillagerListenerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/listener/VillagerListenerTest.kt @@ -2,7 +2,7 @@ package com.github.rushyverse.api.listener import com.github.rushyverse.api.AbstractKoinTest import com.github.rushyverse.api.extension.namespacedKeyKeepJob -import com.github.rushyverse.api.utils.getRandomString +import com.github.rushyverse.api.utils.randomString import io.mockk.every import io.mockk.mockk import io.mockk.slot @@ -31,7 +31,7 @@ class VillagerListenerTest : AbstractKoinTest() { @Test fun `cancel event when tag present in entity`() { - val villager = mockk(getRandomString()) + val villager = mockk(randomString()) val container = mockk() val slotNamespaced = slot() @@ -51,7 +51,7 @@ class VillagerListenerTest : AbstractKoinTest() { @Test fun `not cancel event when tag present in entity`() { - val villager = mockk(getRandomString()) + val villager = mockk(randomString()) val container = mockk() val slotNamespaced = slot() diff --git a/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt b/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt index 59650757..4f0c6449 100644 --- a/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt @@ -2,7 +2,7 @@ package com.github.rushyverse.api.player import com.github.rushyverse.api.AbstractKoinTest import com.github.rushyverse.api.player.exception.ClientNotFoundException -import com.github.rushyverse.api.utils.getRandomString +import com.github.rushyverse.api.utils.randomString import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.CoroutineScope @@ -161,7 +161,7 @@ class ClientManagerImplTest : AbstractKoinTest() { } private fun createPlayerMock(): Player { - val name = getRandomString() + val name = randomString() val player = mockk(name) every { player.name } returns name every { player.uniqueId } returns UUID.randomUUID() diff --git a/src/test/kotlin/com/github/rushyverse/api/schedule/SchedulerTaskTest.kt b/src/test/kotlin/com/github/rushyverse/api/schedule/SchedulerTaskTest.kt index b8189b1c..61249e64 100644 --- a/src/test/kotlin/com/github/rushyverse/api/schedule/SchedulerTaskTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/schedule/SchedulerTaskTest.kt @@ -1,6 +1,6 @@ package com.github.rushyverse.api.schedule -import com.github.rushyverse.api.utils.getRandomString +import com.github.rushyverse.api.utils.randomString import kotlinx.coroutines.* import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested @@ -60,7 +60,7 @@ class SchedulerTaskTest { scheduler.start() val body1: suspend SchedulerTask.Task.() -> Unit = { } - val id1 = getRandomString() + val id1 = randomString() val task1 = scheduler.addAt(0, id1, body1) assertEquals(id1, task1.id) assertEquals(scheduler, task1.parent) @@ -68,7 +68,7 @@ class SchedulerTaskTest { assertEquals(listOf(task1), scheduler.tasks) val body2: suspend SchedulerTask.Task.() -> Unit = { } - val id2 = getRandomString() + val id2 = randomString() val task2 = scheduler.addAt(1, id2, body2) assertEquals(id2, task2.id) assertEquals(scheduler, task2.parent) @@ -76,7 +76,7 @@ class SchedulerTaskTest { assertEquals(listOf(task1, task2), scheduler.tasks) val body3: suspend SchedulerTask.Task.() -> Unit = { } - val id3 = getRandomString() + val id3 = randomString() val task3 = scheduler.addAt(1, id3, body3) assertEquals(id3, task3.id) assertEquals(scheduler, task3.parent) @@ -92,7 +92,7 @@ class SchedulerTaskTest { ) val body1: suspend SchedulerTask.Task.() -> Unit = { } - val id1 = getRandomString() + val id1 = randomString() val task1 = scheduler.addAtUnsafe(0, id1, body1) assertEquals(id1, task1.id) assertEquals(scheduler, task1.parent) @@ -100,7 +100,7 @@ class SchedulerTaskTest { assertEquals(listOf(task1), scheduler.tasks) val body2: suspend SchedulerTask.Task.() -> Unit = { } - val id2 = getRandomString() + val id2 = randomString() val task2 = scheduler.addAtUnsafe(1, id2, body2) assertEquals(id2, task2.id) assertEquals(scheduler, task2.parent) @@ -108,7 +108,7 @@ class SchedulerTaskTest { assertEquals(listOf(task1, task2), scheduler.tasks) val body3: suspend SchedulerTask.Task.() -> Unit = { } - val id3 = getRandomString() + val id3 = randomString() val task3 = scheduler.addAtUnsafe(0, id3, body3) assertEquals(id3, task3.id) assertEquals(scheduler, task3.parent) @@ -126,7 +126,7 @@ class SchedulerTaskTest { scheduler.start() val body1: suspend SchedulerTask.Task.() -> Unit = { } - val id1 = getRandomString() + val id1 = randomString() val task1 = scheduler.add(id1, body1) assertEquals(id1, task1.id) assertEquals(scheduler, task1.parent) @@ -134,7 +134,7 @@ class SchedulerTaskTest { assertEquals(listOf(task1), scheduler.tasks) val body2: suspend SchedulerTask.Task.() -> Unit = { } - val id2 = getRandomString() + val id2 = randomString() val task2 = scheduler.add(id2, body2) assertEquals(id2, task2.id) assertEquals(scheduler, task2.parent) @@ -150,7 +150,7 @@ class SchedulerTaskTest { ) val body1: suspend SchedulerTask.Task.() -> Unit = { } - val id1 = getRandomString() + val id1 = randomString() val task1 = scheduler.addUnsafe(id1, body1) assertEquals(id1, task1.id) assertEquals(scheduler, task1.parent) @@ -158,7 +158,7 @@ class SchedulerTaskTest { assertEquals(listOf(task1), scheduler.tasks) val body2: suspend SchedulerTask.Task.() -> Unit = { } - val id2 = getRandomString() + val id2 = randomString() val task2 = scheduler.addUnsafe(id2, body2) assertEquals(id2, task2.id) assertEquals(scheduler, task2.parent) @@ -332,7 +332,7 @@ class SchedulerTaskTest { scheduler.start() val countDownLatch = CountDownLatch(10) - while(counter <= 1) { + while (counter <= 1) { assertFalse { countDownLatch.await(10, TimeUnit.MILLISECONDS) } countDownLatch.countDown() } @@ -429,7 +429,7 @@ class SchedulerTaskTest { 10.milliseconds, stopWhenNoTask = true ) - val task = scheduler.add { } + val task = scheduler.add { } scheduler.start() assertTrue { scheduler.running } scheduler.remove(task.id) @@ -443,7 +443,7 @@ class SchedulerTaskTest { 10.milliseconds, stopWhenNoTask = false ) - val task = scheduler.add { } + val task = scheduler.add { } scheduler.start() assertTrue { scheduler.running } scheduler.remove(task.id) diff --git a/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt b/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt index f0742d02..00664044 100644 --- a/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt +++ b/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt @@ -7,11 +7,13 @@ import kotlin.random.Random val stringGenerator = generateSequence { UUID.randomUUID().toString() }.distinct().iterator() -fun getRandomString() = stringGenerator.next() +fun randomString() = stringGenerator.next() + +fun randomBoolean() = Random.nextBoolean() const val LIMIT_RANDOM_COORDINATE = 1000.0 -fun createRandomLocation(world: World? = null): Location { +fun randomLocation(world: World? = null): Location { return Location( world, Random.nextDouble(LIMIT_RANDOM_COORDINATE), @@ -20,4 +22,4 @@ fun createRandomLocation(world: World? = null): Location { Random.nextDouble(LIMIT_RANDOM_COORDINATE).toFloat(), Random.nextDouble(LIMIT_RANDOM_COORDINATE).toFloat() ) -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/world/CuboidTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/CuboidTest.kt deleted file mode 100644 index bf200ab0..00000000 --- a/src/test/kotlin/com/github/rushyverse/api/world/CuboidTest.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.github.rushyverse.api.world - -class CuboidTest { - - // TODO Write tests -} From 9287f08e2657d1840396e564c28a8d99cae39c4e Mon Sep 17 00:00:00 2001 From: Distractic Date: Wed, 26 Jul 2023 08:05:49 +0200 Subject: [PATCH 061/143] feat: Add configuration classes --- build.gradle.kts | 4 ++ .../com/github/rushyverse/api/Plugin.kt | 16 +++-- .../reader/IConfigurationReader.kt | 19 ++++++ .../api/configuration/reader/IFileReader.kt | 41 +++++++++++++ .../configuration/reader/YamlFileReader.kt | 58 +++++++++++++++++++ 5 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 src/main/kotlin/com/github/rushyverse/api/configuration/reader/IConfigurationReader.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/configuration/reader/IFileReader.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReader.kt diff --git a/build.gradle.kts b/build.gradle.kts index 96625763..0be2e93e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,6 +15,8 @@ repositories { } dependencies { + val kotlinSerializableVersion = "1.5.1" + val kamlVersion = "0.53.0" val coroutineVersion = "1.6.4" val loggingVersion = "2.1.23" val koinVersion = "3.2.0" @@ -29,6 +31,8 @@ dependencies { implementation(kotlin("stdlib")) implementation(kotlin("stdlib-jdk8")) implementation(kotlin("reflect")) + implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlinSerializableVersion") + implementation("com.charleskorn.kaml:kaml:$kamlVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutineVersion") implementation("io.github.microutils:kotlin-logging:$loggingVersion") diff --git a/src/main/kotlin/com/github/rushyverse/api/Plugin.kt b/src/main/kotlin/com/github/rushyverse/api/Plugin.kt index 678f104d..3bdd3e1d 100644 --- a/src/main/kotlin/com/github/rushyverse/api/Plugin.kt +++ b/src/main/kotlin/com/github/rushyverse/api/Plugin.kt @@ -1,12 +1,18 @@ package com.github.rushyverse.api import com.github.rushyverse.api.APIPlugin.Companion.BUNDLE_API -import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin import com.github.rushyverse.api.extension.registerListener -import com.github.rushyverse.api.koin.* -import com.github.rushyverse.api.listener.* -import com.github.rushyverse.api.player.* -import com.github.rushyverse.api.translation.* +import com.github.rushyverse.api.koin.CraftContext +import com.github.rushyverse.api.koin.loadModule +import com.github.rushyverse.api.listener.PlayerListener +import com.github.rushyverse.api.listener.VillagerListener +import com.github.rushyverse.api.player.Client +import com.github.rushyverse.api.player.ClientManager +import com.github.rushyverse.api.player.ClientManagerImpl +import com.github.rushyverse.api.translation.ResourceBundleTranslationProvider +import com.github.rushyverse.api.translation.SupportedLanguage +import com.github.rushyverse.api.translation.registerResourceBundleForSupportedLocales +import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin import org.bukkit.Bukkit import org.bukkit.entity.Player import org.koin.core.module.Module diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/reader/IConfigurationReader.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/reader/IConfigurationReader.kt new file mode 100644 index 00000000..e61a80e1 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/configuration/reader/IConfigurationReader.kt @@ -0,0 +1,19 @@ +package com.github.rushyverse.api.configuration.reader + +import org.jetbrains.annotations.Blocking + +/** + * Configuration reader. + * Useful to read configuration and transform it to a specific type. + * @param T Final type to obtain after transformation. + */ +public fun interface IConfigurationReader { + + /** + * Read configuration from the given file. + * @param file File to read. + * @return Configuration read from the given file. + */ + @Blocking + public fun readConfiguration(file: String): T +} diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/reader/IFileReader.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/reader/IFileReader.kt new file mode 100644 index 00000000..be857938 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/configuration/reader/IFileReader.kt @@ -0,0 +1,41 @@ +package com.github.rushyverse.api.configuration.reader + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.StringFormat +import kotlinx.serialization.serializer +import kotlin.reflect.KClass + +/** + * Read configuration from YAML file. + * @receiver Configuration reader. + * @param configFile Configuration file to load. + * @return The configuration loaded from the given file. + */ +public inline fun IFileReader.readConfigurationFile(configFile: String): T = + readConfigurationFile(format.serializersModule.serializer(), configFile) + +/** + * Configuration reader. + */ +public interface IFileReader { + + public val format: StringFormat + + /** + * Load the configuration from the given file. + * @param clazz Type of configuration class to load. + * @param filename Configuration file to load based on the "plugins/${plugin.name}" directory. + * So if the plugin name is "MyPlugin" and the config file is "config.yml", the file will be loaded from "plugins/MyPlugin/config.yml". + * @return The configuration loaded from the given file. + */ + public fun readConfigurationFile(clazz: KClass, filename: String): T + + /** + * Load the configuration from the given file. + * @param serializer Serializer to deserialize the configuration to the given type. + * @param filename Configuration file to load based on the "plugins/${plugin.name}" directory. + * So if the plugin name is "MyPlugin" and the config file is "config.yml", the file will be loaded from "plugins/MyPlugin/config.yml". + * @return The configuration loaded from the given file. + */ + public fun readConfigurationFile(serializer: KSerializer, filename: String): T +} diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReader.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReader.kt new file mode 100644 index 00000000..2f32d6ab --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReader.kt @@ -0,0 +1,58 @@ +package com.github.rushyverse.api.configuration.reader + +import com.charleskorn.kaml.Yaml +import kotlinx.serialization.KSerializer +import kotlinx.serialization.StringFormat +import kotlinx.serialization.serializer +import org.bukkit.plugin.java.JavaPlugin +import java.io.File +import kotlin.reflect.KClass +import kotlin.reflect.full.createType + +/** + * Read configuration from YAML file. + * If the file does not exist, it will be created from the resource with the same name. + * @property plugin Plugin to get the data folder from. + * @property format [Yaml] configuration to use. + */ +public class YamlFileReader( + public val plugin: JavaPlugin, + override val format: StringFormat +) : IFileReader { + + override fun readConfigurationFile(clazz: KClass, filename: String): T { + val serializer = format.serializersModule.serializer(clazz.createType()) + @Suppress("UNCHECKED_CAST") + return readConfigurationFile(serializer as KSerializer, filename) + } + + override fun readConfigurationFile(serializer: KSerializer, filename: String): T { + val dataFolder = plugin.dataFolder + require(dataFolder.exists() || dataFolder.mkdirs()) { + "Unable to get or create the plugin data folder ${dataFolder.absoluteFile}." + } + + val config = File(dataFolder, filename) + if (!config.exists()) { + createFileFromResource(config, filename) + } + + return format.decodeFromString(serializer, config.readText()) + } + + /** + * Read the file [filename] from the plugin jar and copy content to [target]. + * @param target File to write the resource to. + * @param filename Name of the resource to copy. + */ + private fun createFileFromResource(target: File, filename: String) { + val resource = plugin::class.java.getResourceAsStream("/$filename") + ?: throw IllegalStateException("Cannot find resource $filename in the plugin jar. Unable to create the configuration file ${target.absoluteFile}.") + + resource.bufferedReader().use { reader -> + target.bufferedWriter().use { writer -> + reader.copyTo(writer) + } + } + } +} From 3cb8a1dc9ae93a813c0ce8f982fac930d045e0be Mon Sep 17 00:00:00 2001 From: Distractic Date: Thu, 27 Jul 2023 13:43:42 +0200 Subject: [PATCH 062/143] chore(gradle): Update dependencies and Java version Updated several library dependencies and the targeted Java version in the build.gradle.kts file. The changes provide more features for testing and lay the groundwork for Kotlin toolchain with the latest LTS Java version. The paper's version was also updated to the latest snapshot for testing purposes, and Kotest and MockBukkit versions were introduced for holistic testing. Java version was also abstracted into variables. --- build.gradle.kts | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0be2e93e..7daf4bf9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + plugins { embeddedKotlin("jvm") embeddedKotlin("plugin.serialization") @@ -21,12 +23,14 @@ dependencies { val loggingVersion = "2.1.23" val koinVersion = "3.2.0" val mccoroutineVersion = "2.4.0" - val paperVersion = "1.19-R0.1-SNAPSHOT" + val paperVersion = "1.20.1-R0.1-SNAPSHOT" + val mockBukkitVersion = "3.18.0" val junitVersion = "5.9.0" val mockkVersion = "1.12.5" val slf4jVersion = "2.0.0-alpha6" val fastboardVersion = "2.0.0" val commandApiVersion = "9.0.3" + val kotestVersion = "5.6.2" implementation(kotlin("stdlib")) implementation(kotlin("stdlib-jdk8")) @@ -62,7 +66,11 @@ dependencies { // Tests testImplementation(kotlin("test-junit5")) testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutineVersion") + testImplementation("io.kotest:kotest-assertions-core:$kotestVersion") + implementation("io.kotest:kotest-assertions-json:$kotestVersion") + testImplementation("io.papermc.paper:paper-api:$paperVersion") + implementation("com.github.seeseemelk:MockBukkit-v1.20:$mockBukkitVersion") testImplementation("org.junit.jupiter:junit-jupiter-params:$junitVersion") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitVersion") testImplementation("io.insert-koin:koin-test:$koinVersion") { @@ -73,8 +81,13 @@ dependencies { testImplementation("org.slf4j:slf4j-simple:$slf4jVersion") } +val javaVersion get() = JavaVersion.VERSION_17 +val javaVersionString get() = javaVersion.toString() +val javaVersionInt get() = javaVersionString.toInt() + kotlin { explicitApi = org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode.Strict + jvmToolchain(javaVersionInt) sourceSets { all { @@ -91,8 +104,13 @@ kotlin { val dokkaOutputDir = "${rootProject.projectDir}/dokka" tasks { - withType { - kotlinOptions.jvmTarget = JavaVersion.VERSION_17.toString() + withType { + kotlinOptions.jvmTarget = javaVersionString + } + + withType { + sourceCompatibility = javaVersionString + targetCompatibility = javaVersionString } test { From 5e3147412199a3e353bf77d636b53a9483ce749f Mon Sep 17 00:00:00 2001 From: Distractic Date: Thu, 27 Jul 2023 13:45:31 +0200 Subject: [PATCH 063/143] chore(koin): Refactor Koin injection and setup The injection method in the CraftContext class has been refactored to enhance the shared instance retrieving between plugins. A new injection method has been added to retrieve instances specific to a plugin id with a fallback option to the APIPlugin id. Changes have also been made to cater to the non-nullability of the Koin instance in the _koins map, eliminating the option of retrieving a null instance. The Koin setup on tests, APIPlugin and the plugin has been modified to reflect the changes in the injection methods and constants. --- .../com/github/rushyverse/api/APIPlugin.kt | 11 +++++--- .../com/github/rushyverse/api/Plugin.kt | 2 -- .../rushyverse/api/koin/CraftContext.kt | 28 ++++++++++++++++--- .../rushyverse/api/listener/PlayerListener.kt | 2 +- .../github/rushyverse/api/player/Client.kt | 2 +- .../github/rushyverse/api/AbstractKoinTest.kt | 21 +++++++------- 6 files changed, 44 insertions(+), 22 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt b/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt index 8dd90929..b9ec0124 100644 --- a/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt +++ b/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt @@ -3,6 +3,7 @@ package com.github.rushyverse.api import com.github.rushyverse.api.koin.CraftContext import com.github.rushyverse.api.koin.loadModule import com.github.rushyverse.api.player.scoreboard.ScoreboardManager +import org.bukkit.Bukkit import org.bukkit.plugin.java.JavaPlugin /** @@ -11,18 +12,20 @@ import org.bukkit.plugin.java.JavaPlugin public class APIPlugin : JavaPlugin() { public companion object { - public const val ID: String = "api" + public const val ID_API: String = "api" public const val BUNDLE_API: String = "api_translate" } override fun onEnable() { - CraftContext.startKoin(ID) - loadModule(ID) { + CraftContext.startKoin(ID_API) + loadModule(ID_API) { + single { Bukkit.getServer() } single { ScoreboardManager() } } } override fun onDisable() { - CraftContext.stopKoin(ID) + CraftContext.stopKoin(ID_API) + super.onDisable() } } diff --git a/src/main/kotlin/com/github/rushyverse/api/Plugin.kt b/src/main/kotlin/com/github/rushyverse/api/Plugin.kt index 3bdd3e1d..63c53257 100644 --- a/src/main/kotlin/com/github/rushyverse/api/Plugin.kt +++ b/src/main/kotlin/com/github/rushyverse/api/Plugin.kt @@ -13,7 +13,6 @@ import com.github.rushyverse.api.translation.ResourceBundleTranslationProvider import com.github.rushyverse.api.translation.SupportedLanguage import com.github.rushyverse.api.translation.registerResourceBundleForSupportedLocales import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin -import org.bukkit.Bukkit import org.bukkit.entity.Player import org.koin.core.module.Module import org.koin.dsl.bind @@ -46,7 +45,6 @@ public abstract class Plugin : SuspendingJavaPlugin() { } protected open fun moduleBukkit(): Module = loadModule(id) { - single { Bukkit.getServer() } single { getLogger() } } diff --git a/src/main/kotlin/com/github/rushyverse/api/koin/CraftContext.kt b/src/main/kotlin/com/github/rushyverse/api/koin/CraftContext.kt index ab9a98f3..d1518906 100644 --- a/src/main/kotlin/com/github/rushyverse/api/koin/CraftContext.kt +++ b/src/main/kotlin/com/github/rushyverse/api/koin/CraftContext.kt @@ -1,5 +1,7 @@ package com.github.rushyverse.api.koin +import com.github.rushyverse.api.APIPlugin +import com.github.rushyverse.api.APIPlugin.Companion.ID_API import org.koin.core.Koin import org.koin.core.KoinApplication import org.koin.core.context.KoinContext @@ -24,7 +26,25 @@ public fun loadModule( return module(createdAtStart, moduleDeclaration).also { CraftContext.loadKoinModules(id, it) } } -public inline fun inject(id: String): Lazy = CraftContext.get(id).inject() +/** + * Injects an instance of the specified type T from the Koin context of the [APIPlugin]. Allows to retrieve + * shared instances between plugins. + * @returnA lazy delegate of type T representing the injected instance. + */ +public inline fun inject(): Lazy = CraftContext.get(ID_API).inject() + +/** + * Injects an instance of the specified type T from the Koin context defined for the [id]. + * The [id] can be the id of the plugin to retrieve instance linked to the plugin. + * If the instance is not found, the [idFallback] will be used to retrieve the instance. + * + * @param id The id of the memory container to retrieve the instance from. + * @param idFallback The id of the memory container to retrieve the instance from if the first one is not found. + * @return A lazy delegate of type T representing the injected instance. + */ +public inline fun inject(id: String, idFallback: String = ID_API): Lazy = lazy { + CraftContext.get(id).getOrNull() ?: CraftContext.get(idFallback).get() +} /** * A copy of [KoinContext] to retrieve koin instance for each application. @@ -37,12 +57,12 @@ public object CraftContext { /** * [Koin] instanced linked to an app id. */ - private val _koins: MutableMap> = mutableMapOf() + private val _koins: MutableMap> = mutableMapOf() /** * [Koin] instanced linked to an app id. */ - public val koins: Map> = _koins + public val koins: Map> = _koins /** * Gets the [Koin] instance for an app. @@ -62,7 +82,7 @@ public object CraftContext { /** Closes and removes the current [Koin] instance. */ public fun stopKoin(id: String): Unit = synchronized(this) { val koinInstance = _koins[id] ?: return@synchronized - koinInstance.second?.close() + koinInstance.second.close() _koins -= id } diff --git a/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt index 0e7b3ceb..ca7f8dc1 100644 --- a/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt +++ b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt @@ -25,7 +25,7 @@ public class PlayerListener( ) : Listener { private val clients: ClientManager by inject(plugin.id) - private val scoreboardManager: ScoreboardManager by inject(APIPlugin.ID) + private val scoreboardManager: ScoreboardManager by inject() /** * Handle the join event to create and store a new client. diff --git a/src/main/kotlin/com/github/rushyverse/api/player/Client.kt b/src/main/kotlin/com/github/rushyverse/api/player/Client.kt index 216f58bc..304fdf38 100644 --- a/src/main/kotlin/com/github/rushyverse/api/player/Client.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/Client.kt @@ -25,7 +25,7 @@ public open class Client( public var lang: SupportedLanguage = SupportedLanguage.ENGLISH ) : CoroutineScope by coroutineScope { - private val scoreboardManager: ScoreboardManager by inject(APIPlugin.ID) + private val scoreboardManager: ScoreboardManager by inject() public val player: Player? by DelegatePlayer(pluginId, playerUUID) diff --git a/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt b/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt index 642837aa..133a072b 100644 --- a/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt @@ -23,17 +23,18 @@ abstract class AbstractKoinTest { open fun onBefore() { pluginId = randomString() CraftContext.startKoin(pluginId) { } - CraftContext.startKoin(APIPlugin.ID) { } + CraftContext.startKoin(APIPlugin.ID_API) { } loadTestModule { - plugin = mockk(randomString()) - server = mockk(randomString()) - - every { plugin.server } returns server - every { plugin.id } returns pluginId - every { plugin.name } returns randomString() - + plugin = mockk(randomString()) { + every { id } returns pluginId + every { name } returns randomString() + } single { plugin } + } + + server = mockk(randomString()) + loadApiTestModule { single { server } } } @@ -41,7 +42,7 @@ abstract class AbstractKoinTest { @AfterTest open fun onAfter() { CraftContext.stopKoin(pluginId) - CraftContext.stopKoin(APIPlugin.ID) + CraftContext.stopKoin(APIPlugin.ID_API) } inline fun testInject(): T = CraftContext.get(pluginId).inject().value @@ -50,6 +51,6 @@ abstract class AbstractKoinTest { loadModule(pluginId, false, moduleDeclaration) fun loadApiTestModule(moduleDeclaration: ModuleDeclaration): Module = - loadModule(APIPlugin.ID, true, moduleDeclaration) + loadModule(APIPlugin.ID_API, true, moduleDeclaration) } From 59d8d792c54bea2b5259087118c7d651f1fa30bd Mon Sep 17 00:00:00 2001 From: Distractic Date: Thu, 27 Jul 2023 13:46:00 +0200 Subject: [PATCH 064/143] chore(config): Refactor interface naming and function documentation Renamed IConfigurationReader and IFileReader to ConfigurationReader and FileReader respectively to align with Kotlin's coding convention. The 'I' prefix is generally not used in Kotlin's interface naming. Changed the documentation of the interface function for better readability and understanding. --- .../{IConfigurationReader.kt => ConfigurationReader.kt} | 4 ++-- .../configuration/reader/{IFileReader.kt => FileReader.kt} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename src/main/kotlin/com/github/rushyverse/api/configuration/reader/{IConfigurationReader.kt => ConfigurationReader.kt} (80%) rename src/main/kotlin/com/github/rushyverse/api/configuration/reader/{IFileReader.kt => FileReader.kt} (100%) diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/reader/IConfigurationReader.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/reader/ConfigurationReader.kt similarity index 80% rename from src/main/kotlin/com/github/rushyverse/api/configuration/reader/IConfigurationReader.kt rename to src/main/kotlin/com/github/rushyverse/api/configuration/reader/ConfigurationReader.kt index e61a80e1..b80dd726 100644 --- a/src/main/kotlin/com/github/rushyverse/api/configuration/reader/IConfigurationReader.kt +++ b/src/main/kotlin/com/github/rushyverse/api/configuration/reader/ConfigurationReader.kt @@ -7,10 +7,10 @@ import org.jetbrains.annotations.Blocking * Useful to read configuration and transform it to a specific type. * @param T Final type to obtain after transformation. */ -public fun interface IConfigurationReader { +public fun interface ConfigurationReader { /** - * Read configuration from the given file. + * Read the configuration from the given file. * @param file File to read. * @return Configuration read from the given file. */ diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/reader/IFileReader.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/reader/FileReader.kt similarity index 100% rename from src/main/kotlin/com/github/rushyverse/api/configuration/reader/IFileReader.kt rename to src/main/kotlin/com/github/rushyverse/api/configuration/reader/FileReader.kt From 08022b24c15dacc839c40829346f23b84637dcce Mon Sep 17 00:00:00 2001 From: Distractic Date: Thu, 27 Jul 2023 13:46:26 +0200 Subject: [PATCH 065/143] fix(area): Refactor CubeArea class constructor and initializer block Simplified CubeArea class constructor declaration and updated the initializer block to enforce the added requirement that both location parameters should be in the same world. This change ensures that the locations passed are consistent, thus avoiding potential issues in the future. --- .../com/github/rushyverse/api/world/CubeArea.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt b/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt index 2e55456c..5f247236 100644 --- a/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt +++ b/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt @@ -10,10 +10,7 @@ import org.bukkit.Location * @property max Maximum position. * @property location Center position of the cube. */ -public class CubeArea( - loc1: Location, - loc2: Location -) { +public class CubeArea(loc1: Location, loc2: Location) { public var location: Location get() = max.centerRelative(min) @@ -31,10 +28,14 @@ public class CubeArea( private set init { + val world1 = loc1.world + val world2 = loc2.world + require(world1 === world2) { "Locations must be in the same world" } + val (x1, x2) = minMaxOf(loc1.x, loc2.x) val (y1, y2) = minMaxOf(loc1.y, loc2.y) val (z1, z2) = minMaxOf(loc1.z, loc2.z) - this.min = Location(loc1.world, x1, y1, z1) - this.max = Location(loc2.world, x2, y2, z2) + this.min = Location(world1, x1, y1, z1) + this.max = Location(world2, x2, y2, z2) } } From 9c64ff68a1938b61ed619eba1a3d671adba1f8d6 Mon Sep 17 00:00:00 2001 From: Distractic Date: Thu, 27 Jul 2023 13:46:59 +0200 Subject: [PATCH 066/143] feat(serial): LocationSerializer and associated tests to API This commit introduces LocationSerializer.kt which provides a way to serialize and deserialize Location objects used in the project. This feature allows for easier handling of Location data when building and handling JSON, serving to improve reliability and maintainability of data manipulation. Tests have been included in this commit to guarantee the correct function of serialization and deserialization methods. --- .../api/serializer/LocationSerializer.kt | 109 ++++++ .../api/serializer/LocationSerializerTest.kt | 325 ++++++++++++++++++ .../github/rushyverse/api/utils/Generator.kt | 8 + 3 files changed, 442 insertions(+) create mode 100644 src/main/kotlin/com/github/rushyverse/api/serializer/LocationSerializer.kt create mode 100644 src/test/kotlin/com/github/rushyverse/api/serializer/LocationSerializerTest.kt diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/LocationSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/LocationSerializer.kt new file mode 100644 index 00000000..bce80699 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/LocationSerializer.kt @@ -0,0 +1,109 @@ +package com.github.rushyverse.api.serializer + +import com.github.rushyverse.api.koin.inject +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException +import kotlinx.serialization.builtins.nullable +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.* +import org.bukkit.Location +import org.bukkit.Server + +/** + * Serializer for [Location]. + */ +public class LocationSerializer : KSerializer { + + /** + * Server instance. + */ + private val server: Server by inject() + + /** + * Serializer for the coordinates x, y or z. + */ + private val coordinateSerializer get() = Double.serializer() + + /** + * Serializer for the rotations yaw or pitch. + */ + private val rotationSerializer get() = Float.serializer().nullable + + /** + * Serializer for the world. + */ + private val worldSerializer get() = String.serializer().nullable + + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("location") { + val coordinateSerializer = coordinateSerializer + val rotationSerializer = rotationSerializer + + element("x", coordinateSerializer.descriptor) + element("y", coordinateSerializer.descriptor) + element("z", coordinateSerializer.descriptor) + element("yaw", rotationSerializer.descriptor) + element("pitch", rotationSerializer.descriptor) + element("world", worldSerializer.descriptor) + } + + override fun serialize(encoder: Encoder, value: Location) { + val coordinateSerializer = coordinateSerializer + val rotationSerializer = rotationSerializer + + encoder.encodeStructure(descriptor) { + encodeSerializableElement(descriptor, 0, coordinateSerializer, value.x) + encodeSerializableElement(descriptor, 1, coordinateSerializer, value.y) + encodeSerializableElement(descriptor, 2, coordinateSerializer, value.z) + encodeSerializableElement(descriptor, 3, rotationSerializer, value.yaw) + encodeSerializableElement(descriptor, 4, rotationSerializer, value.pitch) + encodeSerializableElement(descriptor, 5, worldSerializer, value.world?.name) + } + } + + override fun deserialize(decoder: Decoder): Location { + val coordinateSerializer = coordinateSerializer + val rotationSerializer = rotationSerializer + + return decoder.decodeStructure(descriptor) { + var x: Double? = null + var y: Double? = null + var z: Double? = null + var yaw: Float? = null + var pitch: Float? = null + var world: String? = null + + if (decodeSequentially()) { + x = decodeSerializableElement(descriptor, 0, coordinateSerializer) + y = decodeSerializableElement(descriptor, 1, coordinateSerializer) + z = decodeSerializableElement(descriptor, 2, coordinateSerializer) + yaw = decodeSerializableElement(descriptor, 3, rotationSerializer) + pitch = decodeSerializableElement(descriptor, 4, rotationSerializer) + world = decodeSerializableElement(descriptor, 5, worldSerializer) + } else { + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> x = decodeSerializableElement(descriptor, index, coordinateSerializer) + 1 -> y = decodeSerializableElement(descriptor, index, coordinateSerializer) + 2 -> z = decodeSerializableElement(descriptor, index, coordinateSerializer) + 3 -> yaw = decodeSerializableElement(descriptor, index, rotationSerializer) + 4 -> pitch = decodeSerializableElement(descriptor, index, rotationSerializer) + 5 -> world = decodeSerializableElement(descriptor, index, worldSerializer) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + } + + Location( + world?.let { server.getWorld(it) }, + x ?: throw SerializationException("The field x is missing"), + y ?: throw SerializationException("The field y is missing"), + z ?: throw SerializationException("The field z is missing"), + yaw ?: 0f, + pitch ?: 0f + ) + } + } +} diff --git a/src/test/kotlin/com/github/rushyverse/api/serializer/LocationSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/serializer/LocationSerializerTest.kt new file mode 100644 index 00000000..3e4b7827 --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/serializer/LocationSerializerTest.kt @@ -0,0 +1,325 @@ +package com.github.rushyverse.api.serializer + +import com.github.rushyverse.api.AbstractKoinTest +import com.github.rushyverse.api.utils.randomDouble +import com.github.rushyverse.api.utils.randomFloat +import com.github.rushyverse.api.utils.randomString +import io.kotest.assertions.json.shouldEqualJson +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json +import org.bukkit.Location +import org.bukkit.World +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import kotlin.test.BeforeTest +import kotlin.test.Test + +class LocationSerializerTest : AbstractKoinTest() { + + private lateinit var world: World + + @BeforeTest + override fun onBefore() { + super.onBefore() + val worldName = randomString() + world = mockk() { + every { name } returns worldName + } + + every { server.getWorld(worldName) } returns world + } + + @Nested + inner class Serialize { + + @Nested + inner class OnlyCoordinate { + + @Test + fun `with positive values`() { + assertSerialize(14.0, 2.0, 375.0) + } + + @Test + fun `with negative values`() { + assertSerialize(-58518.0, -7.0, -6828126.0) + } + + @Test + fun `with zero values`() { + assertSerialize(0.0, 0.0, 0.0) + } + + @Test + fun `with decimal values`() { + assertSerialize(0.5, 0.7, 0.6) + } + + @Test + fun `with decimal values and negative values`() { + assertSerialize(-0.5, -0.7, -0.6) + } + + @Test + fun `with mixed values`() { + assertSerialize(0.5, -0.7, 0.6) + } + + } + + @Nested + inner class WithRotation { + + @Test + fun `with positive values`() { + assertSerialize(14.0, 2.0, 375.0, 0.5f, 0.7f) + } + + @Test + fun `with negative values`() { + assertSerialize(-58518.0, -7.0, -6828126.0, -0.5f, -0.7f) + } + + @Test + fun `with zero values`() { + assertSerialize(0.0, 0.0, 0.0, 0.0f, 0.0f) + } + + @Test + fun `with decimal values`() { + assertSerialize(0.5, 0.7, 0.6, 0.5f, 0.7f) + } + + @Test + fun `with decimal values and negative values`() { + assertSerialize(-0.5, -0.7, -0.6, -0.5f, -0.7f) + } + + @Test + fun `with mixed values`() { + assertSerialize(0.5, -0.7, 0.6, 0.1f, -0.2f) + } + + } + + @Test + fun `without world`() { + val loc = Location(null, randomDouble(), randomDouble(), randomDouble(), randomFloat(), randomFloat()) + val json = Json.encodeToString(LocationSerializer(), loc) + json shouldEqualJson """ + { + "x": ${loc.x}, + "y": ${loc.y}, + "z": ${loc.z}, + "yaw": ${loc.yaw}, + "pitch": ${loc.pitch}, + "world": null + } + """.trimIndent() + } + + private fun assertSerialize(x: Double, y: Double, z: Double, yaw: Float = 0f, pitch: Float = 0f) { + val loc = Location(world, x, y, z, yaw, pitch) + val json = Json.encodeToString(LocationSerializer(), loc) + json shouldEqualJson """ + { + "x": $x, + "y": $y, + "z": $z, + "yaw": $yaw, + "pitch": $pitch, + "world": "${world.name}" + } + """.trimIndent() + } + + } + + @Nested + inner class Deserialize { + + @Nested + inner class OnlyCoordinate { + + @Test + fun `with positive values`() { + assertDeserialize(14.0, 2.0, 375.0) + } + + @Test + fun `with negative values`() { + assertDeserialize(-58518.0, -7.0, -6828126.0) + } + + @Test + fun `with zero values`() { + assertDeserialize(0.0, 0.0, 0.0) + } + + @Test + fun `with decimal values`() { + assertDeserialize(0.5, 0.7, 0.6) + } + + @Test + fun `with decimal values and negative values`() { + assertDeserialize(-0.5, -0.7, -0.6) + } + + @Test + fun `with mixed values`() { + assertDeserialize(0.5, -0.7, 0.6) + } + + } + + @Nested + inner class WithRotation { + + @Test + fun `with positive values`() { + assertDeserialize(14.0, 2.0, 375.0, 0.5f, 0.7f) + } + + @Test + fun `with negative values`() { + assertDeserialize(-58518.0, -7.0, -6828126.0, -0.5f, -0.7f) + } + + @Test + fun `with zero values`() { + assertDeserialize(0.0, 0.0, 0.0, 0.0f, 0.0f) + } + + @Test + fun `with decimal values`() { + assertDeserialize(0.5, 0.7, 0.6, 0.5f, 0.7f) + } + + @Test + fun `with decimal values and negative values`() { + assertDeserialize(-0.5, -0.7, -0.6, -0.5f, -0.7f) + } + + @Test + fun `with mixed values`() { + assertDeserialize(0.5, -0.7, 0.6, 0.1f, -0.2f) + } + + } + + @Nested + inner class MissingField { + + @Test + fun `with missing x`() { + val json = """ + { + "y": 0.0, + "z": 0.0, + "yaw": 0.0, + "pitch": 0.0, + "world": "${world.name}" + } + """.trimIndent() + val exception = + assertThrows { Json.decodeFromString(LocationSerializer(), json) } + exception.message shouldBe "The field x is missing" + } + + @Test + fun `with missing y`() { + val json = """ + { + "x": 0.0, + "z": 0.0, + "yaw": 0.0, + "pitch": 0.0, + "world": "${world.name}" + } + """.trimIndent() + val exception = + assertThrows { Json.decodeFromString(LocationSerializer(), json) } + exception.message shouldBe "The field y is missing" + } + + @Test + fun `with missing z`() { + val json = """ + { + "x": 0.0, + "y": 0.0, + "yaw": 0.0, + "pitch": 0.0, + "world": "${world.name}" + } + """.trimIndent() + val exception = + assertThrows { Json.decodeFromString(LocationSerializer(), json) } + exception.message shouldBe "The field z is missing" + } + + @Test + fun `with missing yaw`() { + val json = """ + { + "x": 1.0, + "y": 2.0, + "z": 3.0, + "pitch": 4.0, + "world": "${world.name}" + } + """.trimIndent() + val location = Json.decodeFromString(LocationSerializer(), json) + location shouldBe Location(world, 1.0, 2.0, 3.0, 0.0f, 4.0f) + } + + @Test + fun `with missing pitch`() { + val json = """ + { + "x": 1.0, + "y": 2.0, + "z": 3.0, + "yaw": 4.0, + "world": "${world.name}" + } + """.trimIndent() + val location = Json.decodeFromString(LocationSerializer(), json) + location shouldBe Location(world, 1.0, 2.0, 3.0, 4.0f, 0.0f) + } + + @Test + fun `with missing world`() { + val json = """ + { + "x": 1.0, + "y": 2.0, + "z": 3.0, + "yaw": 4.0, + "pitch": 5.0 + } + """.trimIndent() + val location = Json.decodeFromString(LocationSerializer(), json) + location shouldBe Location(null, 1.0, 2.0, 3.0, 4.0f, 5.0f) + } + } + + private fun assertDeserialize(x: Double, y: Double, z: Double, yaw: Float = 0f, pitch: Float = 0f) { + val json = """ + { + "x": $x, + "y": $y, + "z": $z, + "yaw": $yaw, + "pitch": $pitch + } + """.trimIndent() + val location = Json.decodeFromString(LocationSerializer(), json) + location shouldBe Location(null, x, y, z, yaw, pitch) + } + } +} diff --git a/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt b/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt index 00664044..1957d5cc 100644 --- a/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt +++ b/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt @@ -11,6 +11,14 @@ fun randomString() = stringGenerator.next() fun randomBoolean() = Random.nextBoolean() +fun randomInt() = Random.nextInt() + +fun randomLong() = Random.nextLong() + +fun randomFloat() = Random.nextFloat() + +fun randomDouble() = Random.nextDouble() + const val LIMIT_RANDOM_COORDINATE = 1000.0 fun randomLocation(world: World? = null): Location { From 5b4d28820312a5519b7aa07326f54b326c960e27 Mon Sep 17 00:00:00 2001 From: Distractic Date: Thu, 27 Jul 2023 13:47:37 +0200 Subject: [PATCH 067/143] chore: format --- .../com/github/rushyverse/api/listener/PlayerListener.kt | 4 ++-- src/main/kotlin/com/github/rushyverse/api/player/Client.kt | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt index ca7f8dc1..9e67e936 100644 --- a/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt +++ b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt @@ -1,10 +1,10 @@ package com.github.rushyverse.api.listener -import com.github.rushyverse.api.APIPlugin import com.github.rushyverse.api.Plugin import com.github.rushyverse.api.coroutine.exception.SilentCancellationException import com.github.rushyverse.api.koin.inject -import com.github.rushyverse.api.player.* +import com.github.rushyverse.api.player.Client +import com.github.rushyverse.api.player.ClientManager import com.github.rushyverse.api.player.exception.ClientAlreadyExistsException import com.github.rushyverse.api.player.scoreboard.ScoreboardManager import kotlinx.coroutines.cancel diff --git a/src/main/kotlin/com/github/rushyverse/api/player/Client.kt b/src/main/kotlin/com/github/rushyverse/api/player/Client.kt index 304fdf38..29c0b4d1 100644 --- a/src/main/kotlin/com/github/rushyverse/api/player/Client.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/Client.kt @@ -1,6 +1,5 @@ package com.github.rushyverse.api.player -import com.github.rushyverse.api.APIPlugin import com.github.rushyverse.api.delegate.DelegatePlayer import com.github.rushyverse.api.koin.inject import com.github.rushyverse.api.player.exception.PlayerNotFoundException From e42e0d02a5b205be0b580af155a0c0e0abf346a6 Mon Sep 17 00:00:00 2001 From: Distractic Date: Thu, 27 Jul 2023 13:47:46 +0200 Subject: [PATCH 068/143] chore: format --- .../com/github/rushyverse/api/extension/_CoroutineScope.kt | 4 +++- .../com/github/rushyverse/api/extension/_JavaPlugin.kt | 2 +- .../com/github/rushyverse/api/listener/PlayerListenerTest.kt | 5 ++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt index 8932f268..30828517 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt @@ -1,7 +1,9 @@ package com.github.rushyverse.api.extension import com.github.rushyverse.api.schedule.SchedulerTask -import kotlinx.coroutines.* +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.withContext import kotlin.coroutines.CoroutineContext import kotlin.coroutines.coroutineContext import kotlin.time.Duration diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_JavaPlugin.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_JavaPlugin.kt index 780c54cf..764a0b2d 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_JavaPlugin.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_JavaPlugin.kt @@ -1,7 +1,7 @@ package com.github.rushyverse.api.extension -import com.github.shynixn.mccoroutine.bukkit.registerSuspendingEvents import com.github.rushyverse.api.item.CraftBuilder +import com.github.shynixn.mccoroutine.bukkit.registerSuspendingEvents import org.bukkit.NamespacedKey import org.bukkit.event.Listener import org.bukkit.plugin.java.JavaPlugin diff --git a/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt b/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt index 634abc65..ed453aca 100644 --- a/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt @@ -7,7 +7,10 @@ import com.github.rushyverse.api.player.ClientManagerImpl import com.github.rushyverse.api.player.exception.ClientAlreadyExistsException import com.github.rushyverse.api.player.scoreboard.ScoreboardManager import com.github.rushyverse.api.utils.randomString -import io.mockk.* +import io.mockk.coJustRun +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob From 6a519316b13d765a279a5e9b2cc04625e5c9cb9c Mon Sep 17 00:00:00 2001 From: Quentin Date: Thu, 27 Jul 2023 18:57:49 +0200 Subject: [PATCH 069/143] chore(deps): Set version 1.19 for paper --- build.gradle.kts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 7daf4bf9..1a5f7d02 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,13 +23,12 @@ dependencies { val loggingVersion = "2.1.23" val koinVersion = "3.2.0" val mccoroutineVersion = "2.4.0" - val paperVersion = "1.20.1-R0.1-SNAPSHOT" + val paperVersion = "1.19-R0.1-SNAPSHOT" val mockBukkitVersion = "3.18.0" val junitVersion = "5.9.0" val mockkVersion = "1.12.5" val slf4jVersion = "2.0.0-alpha6" val fastboardVersion = "2.0.0" - val commandApiVersion = "9.0.3" val kotestVersion = "5.6.2" implementation(kotlin("stdlib")) @@ -55,12 +54,6 @@ dependencies { // Scoreboard framework implementation("fr.mrmicky:fastboard:$fastboardVersion") - // CommandAPI framework - // The CommandAPI dependency used for Bukkit and it's forks - api("dev.jorel:commandapi-bukkit-core:$commandApiVersion") - // Due to all functions available in the kotlindsl being inlined, we only need this dependency at compile-time - api("dev.jorel:commandapi-bukkit-kotlin:$commandApiVersion") - api("com.github.Rushyverse:core:6ae31a9250") // Tests From 6cf5454152dcce1460069bc07db003000b257e08 Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 28 Jul 2023 18:50:44 +0200 Subject: [PATCH 070/143] feat(area): Add serializer --- .../github/rushyverse/api/world/CubeArea.kt | 78 ++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt b/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt index 5f247236..2b71cc5e 100644 --- a/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt +++ b/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt @@ -2,17 +2,72 @@ package com.github.rushyverse.api.world import com.github.rushyverse.api.extension.centerRelative import com.github.rushyverse.api.extension.minMaxOf +import com.github.rushyverse.api.serializer.LocationSerializer +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.* import org.bukkit.Location +public object CubeAreaSerializer: KSerializer { + + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("cubeArea") { + val locationDescriptor = LocationSerializer.descriptor + element("loc1", locationDescriptor) + element("loc2", locationDescriptor) + } + + override fun deserialize(decoder: Decoder): CubeArea { + val locationSerializer = LocationSerializer() + + return decoder.decodeStructure(descriptor) { + var loc1: Location? = null + var loc2: Location? = null + + if (decodeSequentially()) { + loc1 = decodeSerializableElement(descriptor, 0, locationSerializer) + loc2 = decodeSerializableElement(descriptor, 1, locationSerializer) + } else { + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> loc1 = decodeSerializableElement(descriptor, index, locationSerializer) + 1 -> loc2 = decodeSerializableElement(descriptor, index, locationSerializer) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + } + + CubeArea( + loc1 ?: throw SerializationException("The field loc1 is missing"), + loc2 ?: throw SerializationException("The field loc2 is missing"), + ) + } + } + + override fun serialize(encoder: Encoder, value: CubeArea) { + val locationSerializer = LocationSerializer() + + encoder.encodeStructure(descriptor) { + encodeSerializableElement(descriptor, 0, locationSerializer, value.min) + encodeSerializableElement(descriptor, 1, locationSerializer, value.max) + } + } + +} + /** * A cuboid area defined by two positions. * @property min Minimum position. * @property max Maximum position. - * @property location Center position of the cube. + * @property center Center position of the cube. */ +@Serializable(with = CubeAreaSerializer::class) public class CubeArea(loc1: Location, loc2: Location) { - public var location: Location + public var center: Location get() = max.centerRelative(min) set(value) { // The new position becomes the center of the cube. @@ -27,6 +82,7 @@ public class CubeArea(loc1: Location, loc2: Location) { public var max: Location private set + init { val world1 = loc1.world val world2 = loc2.world @@ -37,5 +93,23 @@ public class CubeArea(loc1: Location, loc2: Location) { val (z1, z2) = minMaxOf(loc1.z, loc2.z) this.min = Location(world1, x1, y1, z1) this.max = Location(world2, x2, y2, z2) + } + + /** + * Check if a location is in area. + * This method don't care about the equality between the worlds names. + * Reason: log: "CubeArea: Worlds are not equal: plugins/rtf/temp/rtf1 -> null" + */ + public fun isInArea(location: Location): Boolean { + return min.world === location.world && + location.x in min.x..max.x && + location.y in min.y..max.y && + location.z in min.z..max.z + } + + override fun toString(): String { + return "CubeArea(min=$min, max=$max)" + } + } From 97af2a0c69bac41419280edacf7e91b083846a6f Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 28 Jul 2023 18:51:16 +0200 Subject: [PATCH 071/143] chore: Set static descriptor serializer --- .../api/serializer/LocationSerializer.kt | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/LocationSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/LocationSerializer.kt index bce80699..d13388d7 100644 --- a/src/main/kotlin/com/github/rushyverse/api/serializer/LocationSerializer.kt +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/LocationSerializer.kt @@ -16,38 +16,44 @@ import org.bukkit.Server */ public class LocationSerializer : KSerializer { - /** - * Server instance. - */ - private val server: Server by inject() + public companion object { - /** - * Serializer for the coordinates x, y or z. - */ - private val coordinateSerializer get() = Double.serializer() + /** + * Serializer for the coordinates x, y or z. + */ + private val coordinateSerializer get() = Double.serializer() - /** - * Serializer for the rotations yaw or pitch. - */ - private val rotationSerializer get() = Float.serializer().nullable + /** + * Serializer for the rotations yaw or pitch. + */ + private val rotationSerializer get() = Float.serializer().nullable - /** - * Serializer for the world. - */ - private val worldSerializer get() = String.serializer().nullable + /** + * Serializer for the world. + */ + private val worldSerializer get() = String.serializer().nullable - override val descriptor: SerialDescriptor = buildClassSerialDescriptor("location") { - val coordinateSerializer = coordinateSerializer - val rotationSerializer = rotationSerializer + public val descriptor: SerialDescriptor = buildClassSerialDescriptor("location") { + val coordinateSerializer = coordinateSerializer + val rotationSerializer = rotationSerializer + + element("x", coordinateSerializer.descriptor) + element("y", coordinateSerializer.descriptor) + element("z", coordinateSerializer.descriptor) + element("yaw", rotationSerializer.descriptor) + element("pitch", rotationSerializer.descriptor) + element("world", worldSerializer.descriptor) + } - element("x", coordinateSerializer.descriptor) - element("y", coordinateSerializer.descriptor) - element("z", coordinateSerializer.descriptor) - element("yaw", rotationSerializer.descriptor) - element("pitch", rotationSerializer.descriptor) - element("world", worldSerializer.descriptor) } + /** + * Server instance. + */ + private val server: Server by inject() + + override val descriptor: SerialDescriptor = LocationSerializer.descriptor + override fun serialize(encoder: Encoder, value: Location) { val coordinateSerializer = coordinateSerializer val rotationSerializer = rotationSerializer From d29a6d96463a80e224728301ce727dd6375dd42f Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 28 Jul 2023 18:54:17 +0200 Subject: [PATCH 072/143] chore: Remove debug print --- .../kotlin/com/github/rushyverse/api/listener/PlayerListener.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt index 9e67e936..39f77569 100644 --- a/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt +++ b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt @@ -60,7 +60,6 @@ public class PlayerListener( @EventHandler(priority = EventPriority.HIGHEST) public suspend fun onQuit(event: PlayerQuitEvent) { val player = event.player - println(scoreboardManager) scoreboardManager.remove(player) val client = clients.removeClient(player) ?: return From 05f570a57a20144a1aed1a78ae553cff24c69cfc Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 28 Jul 2023 18:55:07 +0200 Subject: [PATCH 073/143] chore: Load koin module for SharedGameData --- src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt b/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt index b9ec0124..1d49d241 100644 --- a/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt +++ b/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt @@ -1,5 +1,6 @@ package com.github.rushyverse.api +import com.github.rushyverse.api.game.SharedGameData import com.github.rushyverse.api.koin.CraftContext import com.github.rushyverse.api.koin.loadModule import com.github.rushyverse.api.player.scoreboard.ScoreboardManager @@ -17,10 +18,12 @@ public class APIPlugin : JavaPlugin() { } override fun onEnable() { + super.onEnable() CraftContext.startKoin(ID_API) loadModule(ID_API) { single { Bukkit.getServer() } single { ScoreboardManager() } + single { SharedGameData() } } } From 23cc436fbdb3473919f1f16b3800e03ab397613c Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 28 Jul 2023 18:55:41 +0200 Subject: [PATCH 074/143] chore: Call koin module for clients on enable --- src/main/kotlin/com/github/rushyverse/api/Plugin.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/kotlin/com/github/rushyverse/api/Plugin.kt b/src/main/kotlin/com/github/rushyverse/api/Plugin.kt index 63c53257..20433aee 100644 --- a/src/main/kotlin/com/github/rushyverse/api/Plugin.kt +++ b/src/main/kotlin/com/github/rushyverse/api/Plugin.kt @@ -9,6 +9,7 @@ import com.github.rushyverse.api.listener.VillagerListener import com.github.rushyverse.api.player.Client import com.github.rushyverse.api.player.ClientManager import com.github.rushyverse.api.player.ClientManagerImpl +import com.github.rushyverse.api.player.scoreboard.ScoreboardManager import com.github.rushyverse.api.translation.ResourceBundleTranslationProvider import com.github.rushyverse.api.translation.SupportedLanguage import com.github.rushyverse.api.translation.registerResourceBundleForSupportedLocales @@ -30,6 +31,7 @@ public abstract class Plugin : SuspendingJavaPlugin() { CraftContext.startKoin(id) moduleBukkit() + moduleClients() registerListener { PlayerListener(this) } registerListener { VillagerListener(this) } From 622f5ee320b435ee8dac01b09fc152bae649f763 Mon Sep 17 00:00:00 2001 From: Distractic Date: Fri, 28 Jul 2023 19:31:11 +0200 Subject: [PATCH 075/143] docs: Add documentation --- .../com/github/rushyverse/api/world/CubeArea.kt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt b/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt index 2b71cc5e..f068320e 100644 --- a/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt +++ b/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt @@ -11,6 +11,9 @@ import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.encoding.* import org.bukkit.Location +/** + * Serializer class for [CubeArea] objects. + */ public object CubeAreaSerializer: KSerializer { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("cubeArea") { @@ -20,7 +23,7 @@ public object CubeAreaSerializer: KSerializer { } override fun deserialize(decoder: Decoder): CubeArea { - val locationSerializer = LocationSerializer() + val locationSerializer = LocationSerializer return decoder.decodeStructure(descriptor) { var loc1: Location? = null @@ -48,7 +51,7 @@ public object CubeAreaSerializer: KSerializer { } override fun serialize(encoder: Encoder, value: CubeArea) { - val locationSerializer = LocationSerializer() + val locationSerializer = LocationSerializer encoder.encodeStructure(descriptor) { encodeSerializableElement(descriptor, 0, locationSerializer, value.min) @@ -93,13 +96,13 @@ public class CubeArea(loc1: Location, loc2: Location) { val (z1, z2) = minMaxOf(loc1.z, loc2.z) this.min = Location(world1, x1, y1, z1) this.max = Location(world2, x2, y2, z2) - } /** - * Check if a location is in area. - * This method don't care about the equality between the worlds names. - * Reason: log: "CubeArea: Worlds are not equal: plugins/rtf/temp/rtf1 -> null" + * Determines if a given location is within the specified area. + * + * @param location The location to check. + * @return True if the location is within the area, false otherwise. */ public fun isInArea(location: Location): Boolean { return min.world === location.world && From ab88125324746d78867c56a807ac7376b892ba6a Mon Sep 17 00:00:00 2001 From: Distractic Date: Fri, 28 Jul 2023 19:34:39 +0200 Subject: [PATCH 076/143] fix: Transform location to object --- .../api/serializer/LocationSerializer.kt | 60 ++++++++----------- .../api/serializer/LocationSerializerTest.kt | 44 +++++++------- 2 files changed, 46 insertions(+), 58 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/LocationSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/LocationSerializer.kt index d13388d7..43c47da7 100644 --- a/src/main/kotlin/com/github/rushyverse/api/serializer/LocationSerializer.kt +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/LocationSerializer.kt @@ -1,6 +1,5 @@ package com.github.rushyverse.api.serializer -import com.github.rushyverse.api.koin.inject import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException import kotlinx.serialization.builtins.nullable @@ -8,51 +7,40 @@ import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.encoding.* +import org.bukkit.Bukkit import org.bukkit.Location -import org.bukkit.Server /** * Serializer for [Location]. */ -public class LocationSerializer : KSerializer { +public object LocationSerializer : KSerializer { - public companion object { - - /** - * Serializer for the coordinates x, y or z. - */ - private val coordinateSerializer get() = Double.serializer() - - /** - * Serializer for the rotations yaw or pitch. - */ - private val rotationSerializer get() = Float.serializer().nullable - - /** - * Serializer for the world. - */ - private val worldSerializer get() = String.serializer().nullable - - public val descriptor: SerialDescriptor = buildClassSerialDescriptor("location") { - val coordinateSerializer = coordinateSerializer - val rotationSerializer = rotationSerializer - - element("x", coordinateSerializer.descriptor) - element("y", coordinateSerializer.descriptor) - element("z", coordinateSerializer.descriptor) - element("yaw", rotationSerializer.descriptor) - element("pitch", rotationSerializer.descriptor) - element("world", worldSerializer.descriptor) - } + /** + * Serializer for the coordinates x, y or z. + */ + private val coordinateSerializer get() = Double.serializer() - } + /** + * Serializer for the rotations yaw or pitch. + */ + private val rotationSerializer get() = Float.serializer().nullable /** - * Server instance. + * Serializer for the world. */ - private val server: Server by inject() + private val worldSerializer get() = String.serializer().nullable - override val descriptor: SerialDescriptor = LocationSerializer.descriptor + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("location") { + val coordinateSerializer = coordinateSerializer + val rotationSerializer = rotationSerializer + + element("x", coordinateSerializer.descriptor) + element("y", coordinateSerializer.descriptor) + element("z", coordinateSerializer.descriptor) + element("yaw", rotationSerializer.descriptor) + element("pitch", rotationSerializer.descriptor) + element("world", worldSerializer.descriptor) + } override fun serialize(encoder: Encoder, value: Location) { val coordinateSerializer = coordinateSerializer @@ -103,7 +91,7 @@ public class LocationSerializer : KSerializer { } Location( - world?.let { server.getWorld(it) }, + world?.let { Bukkit.getServer().getWorld(it) }, x ?: throw SerializationException("The field x is missing"), y ?: throw SerializationException("The field y is missing"), z ?: throw SerializationException("The field z is missing"), diff --git a/src/test/kotlin/com/github/rushyverse/api/serializer/LocationSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/serializer/LocationSerializerTest.kt index 3e4b7827..febed40b 100644 --- a/src/test/kotlin/com/github/rushyverse/api/serializer/LocationSerializerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/serializer/LocationSerializerTest.kt @@ -1,35 +1,35 @@ package com.github.rushyverse.api.serializer -import com.github.rushyverse.api.AbstractKoinTest +import be.seeseemelk.mockbukkit.MockBukkit +import be.seeseemelk.mockbukkit.WorldMock import com.github.rushyverse.api.utils.randomDouble import com.github.rushyverse.api.utils.randomFloat -import com.github.rushyverse.api.utils.randomString import io.kotest.assertions.json.shouldEqualJson import io.kotest.matchers.shouldBe -import io.mockk.every -import io.mockk.mockk import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json import org.bukkit.Location -import org.bukkit.World import org.junit.jupiter.api.Nested import org.junit.jupiter.api.assertThrows +import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test -class LocationSerializerTest : AbstractKoinTest() { +class LocationSerializerTest { - private lateinit var world: World + private lateinit var world: WorldMock @BeforeTest - override fun onBefore() { - super.onBefore() - val worldName = randomString() - world = mockk() { - every { name } returns worldName + fun onBefore() { + world = WorldMock() + MockBukkit.mock().apply { + addWorld(world) } + } - every { server.getWorld(worldName) } returns world + @AfterTest + fun onAfter() { + MockBukkit.unmock() } @Nested @@ -108,7 +108,7 @@ class LocationSerializerTest : AbstractKoinTest() { @Test fun `without world`() { val loc = Location(null, randomDouble(), randomDouble(), randomDouble(), randomFloat(), randomFloat()) - val json = Json.encodeToString(LocationSerializer(), loc) + val json = Json.encodeToString(LocationSerializer, loc) json shouldEqualJson """ { "x": ${loc.x}, @@ -123,7 +123,7 @@ class LocationSerializerTest : AbstractKoinTest() { private fun assertSerialize(x: Double, y: Double, z: Double, yaw: Float = 0f, pitch: Float = 0f) { val loc = Location(world, x, y, z, yaw, pitch) - val json = Json.encodeToString(LocationSerializer(), loc) + val json = Json.encodeToString(LocationSerializer, loc) json shouldEqualJson """ { "x": $x, @@ -226,7 +226,7 @@ class LocationSerializerTest : AbstractKoinTest() { } """.trimIndent() val exception = - assertThrows { Json.decodeFromString(LocationSerializer(), json) } + assertThrows { Json.decodeFromString(LocationSerializer, json) } exception.message shouldBe "The field x is missing" } @@ -242,7 +242,7 @@ class LocationSerializerTest : AbstractKoinTest() { } """.trimIndent() val exception = - assertThrows { Json.decodeFromString(LocationSerializer(), json) } + assertThrows { Json.decodeFromString(LocationSerializer, json) } exception.message shouldBe "The field y is missing" } @@ -258,7 +258,7 @@ class LocationSerializerTest : AbstractKoinTest() { } """.trimIndent() val exception = - assertThrows { Json.decodeFromString(LocationSerializer(), json) } + assertThrows { Json.decodeFromString(LocationSerializer, json) } exception.message shouldBe "The field z is missing" } @@ -273,7 +273,7 @@ class LocationSerializerTest : AbstractKoinTest() { "world": "${world.name}" } """.trimIndent() - val location = Json.decodeFromString(LocationSerializer(), json) + val location = Json.decodeFromString(LocationSerializer, json) location shouldBe Location(world, 1.0, 2.0, 3.0, 0.0f, 4.0f) } @@ -288,7 +288,7 @@ class LocationSerializerTest : AbstractKoinTest() { "world": "${world.name}" } """.trimIndent() - val location = Json.decodeFromString(LocationSerializer(), json) + val location = Json.decodeFromString(LocationSerializer, json) location shouldBe Location(world, 1.0, 2.0, 3.0, 4.0f, 0.0f) } @@ -303,7 +303,7 @@ class LocationSerializerTest : AbstractKoinTest() { "pitch": 5.0 } """.trimIndent() - val location = Json.decodeFromString(LocationSerializer(), json) + val location = Json.decodeFromString(LocationSerializer, json) location shouldBe Location(null, 1.0, 2.0, 3.0, 4.0f, 5.0f) } } @@ -318,7 +318,7 @@ class LocationSerializerTest : AbstractKoinTest() { "pitch": $pitch } """.trimIndent() - val location = Json.decodeFromString(LocationSerializer(), json) + val location = Json.decodeFromString(LocationSerializer, json) location shouldBe Location(null, x, y, z, yaw, pitch) } } From 2b0ef6355550df1663de0eaa1dc3319b8e9ec053 Mon Sep 17 00:00:00 2001 From: Distractic Date: Fri, 28 Jul 2023 21:49:02 +0200 Subject: [PATCH 077/143] tests: Add tests for yaml reader --- .../reader/YamlFileReaderTest.kt | 129 ++++++++++++++++++ .../resources/configuration/fake_config.yml | 1 + src/test/resources/fake_config.yml | 1 + 3 files changed, 131 insertions(+) create mode 100644 src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt create mode 100644 src/test/resources/configuration/fake_config.yml create mode 100644 src/test/resources/fake_config.yml diff --git a/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt b/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt new file mode 100644 index 00000000..f105e718 --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt @@ -0,0 +1,129 @@ +package com.github.rushyverse.api.configuration.reader + +import com.charleskorn.kaml.Yaml +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException +import org.bukkit.plugin.java.JavaPlugin +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.io.TempDir +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource +import java.io.File +import kotlin.test.BeforeTest +import kotlin.test.Test + +class YamlFileReaderTest { + + @Serializable + data class TestValue(val test: String) + + private lateinit var plugin: JavaPlugin + private lateinit var reader: YamlFileReader + + @TempDir + lateinit var tempDir: File + + @BeforeTest + fun onBefore() { + plugin = mockk() + reader = YamlFileReader(plugin, Yaml.default) + } + + @Nested + inner class ReadConfigurationFileFromClass { + + @Test + fun `should throw if class is not serializable`() { + class Test + shouldThrow { + reader.readConfigurationFile(Test::class, "test.yml") + } + } + + @ParameterizedTest + @CsvSource( + "fake_config.yml, withoutParent", + "configuration/fake_config.yml, withParent" + ) + fun `should create file and decode it`(configFile: String, value: String) { + shouldCreateFileAndDecode(configFile, value) { reader.readConfigurationFile(TestValue::class, it) } + } + + @Test + fun `should not create file if exists and read it`() { + shouldNotCreateFileIfExistsAndReadIt { reader.readConfigurationFile(TestValue::class, it) } + } + } + + @Nested + inner class ReadConfigurationFileFromSerializer { + + @Test + fun `should throw if plugin folder cannot be created`() { + every { plugin.dataFolder } returns mockk { + every { exists() } returns false + every { mkdirs() } returns false + every { absolutePath } returns "test" + } + + val exception = shouldThrow { + reader.readConfigurationFile(mockk>(), "test.yml") + } + + exception.message shouldBe "Unable to get or create the plugin data folder test." + } + + @ParameterizedTest + @CsvSource( + "fake_config.yml, withoutParent", + "configuration/fake_config.yml, withParent" + ) + fun `should create file and decode it`(configFile: String, value: String) { + shouldCreateFileAndDecode(configFile, value) { reader.readConfigurationFile(TestValue.serializer(), it) } + } + + @Test + fun `should not create file if exists and read it`() { + shouldNotCreateFileIfExistsAndReadIt { reader.readConfigurationFile(TestValue.serializer(), it) } + } + } + + fun shouldCreateFileAndDecode( + configFile: String, + value: String, + load: (String) -> TestValue + ) { + every { plugin.dataFolder } returns tempDir + + val expectedValue = TestValue(value) + load(configFile) shouldBe expectedValue + + val file = File(tempDir, configFile) + file.exists() shouldBe true + file.readText() shouldBe "test: $value\r\n" + } + + fun shouldNotCreateFileIfExistsAndReadIt( + load: (String) -> TestValue + ) { + every { plugin.dataFolder } returns tempDir + + val value = "This is a custom value" + + val configFile = "fake_config.yml" + val file = File(tempDir, configFile) + val content = "test: $value" + file.writeText(content) + + val expectedValue = TestValue(value) + load(configFile) shouldBe expectedValue + + file.readText() shouldBe content + } + +} diff --git a/src/test/resources/configuration/fake_config.yml b/src/test/resources/configuration/fake_config.yml new file mode 100644 index 00000000..bf39ef68 --- /dev/null +++ b/src/test/resources/configuration/fake_config.yml @@ -0,0 +1 @@ +test: withParent diff --git a/src/test/resources/fake_config.yml b/src/test/resources/fake_config.yml new file mode 100644 index 00000000..3c2d5a08 --- /dev/null +++ b/src/test/resources/fake_config.yml @@ -0,0 +1 @@ +test: withoutParent From ffafdca0d7e1efb353e8d4dd386e569af7d43424 Mon Sep 17 00:00:00 2001 From: Distractic Date: Fri, 28 Jul 2023 21:59:23 +0200 Subject: [PATCH 078/143] fix: Create parent of file if necessary --- .../configuration/reader/YamlFileReader.kt | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReader.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReader.kt index 2f32d6ab..0bcbf402 100644 --- a/src/main/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReader.kt +++ b/src/main/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReader.kt @@ -5,7 +5,9 @@ import kotlinx.serialization.KSerializer import kotlinx.serialization.StringFormat import kotlinx.serialization.serializer import org.bukkit.plugin.java.JavaPlugin +import org.jetbrains.annotations.Blocking import java.io.File +import kotlin.io.path.createParentDirectories import kotlin.reflect.KClass import kotlin.reflect.full.createType @@ -20,34 +22,53 @@ public class YamlFileReader( override val format: StringFormat ) : IFileReader { + @Blocking override fun readConfigurationFile(clazz: KClass, filename: String): T { val serializer = format.serializersModule.serializer(clazz.createType()) @Suppress("UNCHECKED_CAST") return readConfigurationFile(serializer as KSerializer, filename) } + @Blocking override fun readConfigurationFile(serializer: KSerializer, filename: String): T { val dataFolder = plugin.dataFolder require(dataFolder.exists() || dataFolder.mkdirs()) { - "Unable to get or create the plugin data folder ${dataFolder.absoluteFile}." + "Unable to get or create the plugin data folder ${dataFolder.absolutePath}." } val config = File(dataFolder, filename) - if (!config.exists()) { - createFileFromResource(config, filename) - } + createConfigurationFileIfNecessary(config, filename) return format.decodeFromString(serializer, config.readText()) } /** - * Read the file [filename] from the plugin jar and copy content to [target]. + * Creates the [configuration][config] file if it does not exist. + * Will create the parent directories if necessary. + * + * @param config The configuration file to create. + * @param resourceFile The resource file to copy if the configuration file does not exist. + */ + private fun createConfigurationFileIfNecessary(config: File, resourceFile: String) { + if(config.exists()) return + + config.toPath().createParentDirectories() + require(config.createNewFile()) { + "Unable to create the configuration file ${config.absoluteFile}." + } + + createFileFromResource(config, resourceFile) + } + + /** + * Read the file [resourceFile] from the plugin jar and copy content to [target]. * @param target File to write the resource to. - * @param filename Name of the resource to copy. + * @param resourceFile Name of the resource to copy. */ - private fun createFileFromResource(target: File, filename: String) { - val resource = plugin::class.java.getResourceAsStream("/$filename") - ?: throw IllegalStateException("Cannot find resource $filename in the plugin jar. Unable to create the configuration file ${target.absoluteFile}.") + @Blocking + private fun createFileFromResource(target: File, resourceFile: String) { + val resource = plugin::class.java.getResourceAsStream("/$resourceFile") + ?: throw IllegalStateException("Cannot find resource $resourceFile in the plugin.") resource.bufferedReader().use { reader -> target.bufferedWriter().use { writer -> From 28946596a7bff3aa18a3d5b0e8b3f864fcd8c6d1 Mon Sep 17 00:00:00 2001 From: Distractic Date: Fri, 28 Jul 2023 21:59:49 +0200 Subject: [PATCH 079/143] chore: remove print --- .../kotlin/com/github/rushyverse/api/listener/PlayerListener.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt index 9e67e936..39f77569 100644 --- a/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt +++ b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt @@ -60,7 +60,6 @@ public class PlayerListener( @EventHandler(priority = EventPriority.HIGHEST) public suspend fun onQuit(event: PlayerQuitEvent) { val player = event.player - println(scoreboardManager) scoreboardManager.remove(player) val client = clients.removeClient(player) ?: return From 6d1e91c6ea7708a24d07e264f5fe32e23eb189c1 Mon Sep 17 00:00:00 2001 From: Distractic Date: Fri, 28 Jul 2023 22:21:05 +0200 Subject: [PATCH 080/143] chore: Use Bukkit server to retrieve data --- .../rushyverse/api/delegate/DelegatePlayer.kt | 12 ++---- .../rushyverse/api/delegate/DelegateWorld.kt | 12 ++---- .../github/rushyverse/api/player/Client.kt | 3 +- .../api/delegate/DelegateWorldTest.kt | 37 +++++++++++-------- .../api/delegate/PlayerWorldTest.kt | 33 +++++++++++------ .../api/listener/PlayerListenerTest.kt | 4 +- .../api/player/ClientManagerImplTest.kt | 2 +- .../rushyverse/api/player/ClientTest.kt | 8 ++-- 8 files changed, 59 insertions(+), 52 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/delegate/DelegatePlayer.kt b/src/main/kotlin/com/github/rushyverse/api/delegate/DelegatePlayer.kt index 3585853d..6b149293 100644 --- a/src/main/kotlin/com/github/rushyverse/api/delegate/DelegatePlayer.kt +++ b/src/main/kotlin/com/github/rushyverse/api/delegate/DelegatePlayer.kt @@ -1,7 +1,6 @@ package com.github.rushyverse.api.delegate -import com.github.rushyverse.api.koin.inject -import org.bukkit.Server +import org.bukkit.Bukkit import org.bukkit.entity.Player import java.util.* import kotlin.properties.ReadOnlyProperty @@ -10,13 +9,10 @@ import kotlin.reflect.KProperty /** * Class to delegate the retrieve of a player through Bukkit. * @property uuid Player's UUID. - * @property server Server to find the player. */ -public class DelegatePlayer(pluginId: String, public val uuid: UUID) : ReadOnlyProperty { - - private val server: Server by inject(pluginId) +public class DelegatePlayer(public val uuid: UUID) : ReadOnlyProperty { override operator fun getValue(thisRef: Any?, property: KProperty<*>): Player? { - return server.getPlayer(uuid) + return Bukkit.getPlayer(uuid) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/delegate/DelegateWorld.kt b/src/main/kotlin/com/github/rushyverse/api/delegate/DelegateWorld.kt index 664fc85e..f1210a25 100644 --- a/src/main/kotlin/com/github/rushyverse/api/delegate/DelegateWorld.kt +++ b/src/main/kotlin/com/github/rushyverse/api/delegate/DelegateWorld.kt @@ -1,7 +1,6 @@ package com.github.rushyverse.api.delegate -import com.github.rushyverse.api.koin.inject -import org.bukkit.Server +import org.bukkit.Bukkit import org.bukkit.World import java.util.* import kotlin.properties.ReadOnlyProperty @@ -10,13 +9,10 @@ import kotlin.reflect.KProperty /** * Class to delegate the retrieve of a world through Bukkit. * @property uuid World's UUID. - * @property server Server to find the world. */ -public class DelegateWorld(pluginId: String, public val uuid: UUID) : ReadOnlyProperty { - - public val server: Server by inject(pluginId) +public class DelegateWorld(public val uuid: UUID) : ReadOnlyProperty { override operator fun getValue(thisRef: Any?, property: KProperty<*>): World? { - return server.getWorld(uuid) + return Bukkit.getWorld(uuid) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/player/Client.kt b/src/main/kotlin/com/github/rushyverse/api/player/Client.kt index 29c0b4d1..9887a1c4 100644 --- a/src/main/kotlin/com/github/rushyverse/api/player/Client.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/Client.kt @@ -18,7 +18,6 @@ import java.util.* * @property player Player linked to the client. */ public open class Client( - pluginId: String, public val playerUUID: UUID, coroutineScope: CoroutineScope, public var lang: SupportedLanguage = SupportedLanguage.ENGLISH @@ -26,7 +25,7 @@ public open class Client( private val scoreboardManager: ScoreboardManager by inject() - public val player: Player? by DelegatePlayer(pluginId, playerUUID) + public val player: Player? by DelegatePlayer(playerUUID) /** * Retrieve the instance of player. diff --git a/src/test/kotlin/com/github/rushyverse/api/delegate/DelegateWorldTest.kt b/src/test/kotlin/com/github/rushyverse/api/delegate/DelegateWorldTest.kt index b489457d..2d22c2ca 100644 --- a/src/test/kotlin/com/github/rushyverse/api/delegate/DelegateWorldTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/delegate/DelegateWorldTest.kt @@ -1,23 +1,31 @@ package com.github.rushyverse.api.delegate -import com.github.rushyverse.api.AbstractKoinTest -import io.mockk.every -import io.mockk.mockk +import be.seeseemelk.mockbukkit.MockBukkit +import be.seeseemelk.mockbukkit.WorldMock import org.bukkit.World import java.util.* -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNull +import kotlin.test.* -class DelegateWorldTest : AbstractKoinTest() { +class DelegateWorldTest { + + private lateinit var world: WorldMock + + @BeforeTest + fun onBefore() { + world = WorldMock() + MockBukkit.mock().apply { + addWorld(world) + } + } + + @AfterTest + fun onAfter() { + MockBukkit.unmock() + } @Test fun `get world if server has it`() { - val uuid = UUID.randomUUID() - val delegate = DelegateWorld(pluginId, uuid) - - val world = mockk() - every { server.getWorld(uuid) } returns world + val delegate = DelegateWorld(world.uid) val obj = object { val property: World? by delegate @@ -29,8 +37,7 @@ class DelegateWorldTest : AbstractKoinTest() { @Test fun `get world if server doesn't have it`() { val uuid = UUID.randomUUID() - val delegate = DelegateWorld(pluginId, uuid) - every { server.getWorld(uuid) } returns null + val delegate = DelegateWorld(uuid) val obj = object { val property: World? by delegate @@ -38,4 +45,4 @@ class DelegateWorldTest : AbstractKoinTest() { assertNull(obj.property) } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/delegate/PlayerWorldTest.kt b/src/test/kotlin/com/github/rushyverse/api/delegate/PlayerWorldTest.kt index d2a4ffd7..3fd0f653 100644 --- a/src/test/kotlin/com/github/rushyverse/api/delegate/PlayerWorldTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/delegate/PlayerWorldTest.kt @@ -1,23 +1,33 @@ package com.github.rushyverse.api.delegate -import com.github.rushyverse.api.AbstractKoinTest -import io.mockk.every -import io.mockk.mockk +import be.seeseemelk.mockbukkit.MockBukkit import org.bukkit.entity.Player import org.junit.jupiter.api.Test import java.util.* +import kotlin.test.AfterTest +import kotlin.test.BeforeTest import kotlin.test.assertEquals import kotlin.test.assertNull -class PlayerWorldTest : AbstractKoinTest() { +class PlayerWorldTest { + + private lateinit var player: Player + + @BeforeTest + fun onBefore() { + MockBukkit.mock().apply { + player = addPlayer() + } + } + + @AfterTest + fun onAfter() { + MockBukkit.unmock() + } @Test fun `get player if server has it`() { - val uuid = UUID.randomUUID() - val delegate = DelegatePlayer(pluginId, uuid) - - val player = mockk() - every { server.getPlayer(uuid) } returns player + val delegate = DelegatePlayer(player.uniqueId) val obj = object { val property by delegate @@ -29,8 +39,7 @@ class PlayerWorldTest : AbstractKoinTest() { @Test fun `get player if server doesn't have it`() { val uuid = UUID.randomUUID() - val delegate = DelegatePlayer(pluginId, uuid) - every { server.getPlayer(uuid) } returns null + val delegate = DelegatePlayer(uuid) val obj = object { val property by delegate @@ -38,4 +47,4 @@ class PlayerWorldTest : AbstractKoinTest() { assertNull(obj.property) } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt b/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt index ed453aca..6dd5fba1 100644 --- a/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt @@ -116,7 +116,7 @@ class PlayerListenerTest : AbstractKoinTest() { val player = createPlayerMock() coJustRun { scoreboardManagerMock.remove(any()) } - val client = Client(pluginId, player.uniqueId, CoroutineScope(Dispatchers.Main + SupervisorJob())) + val client = Client(player.uniqueId, CoroutineScope(Dispatchers.Main + SupervisorJob())) clientManager.put(player, client) listener.onQuit(createEvent(player)) @@ -141,5 +141,5 @@ class PlayerListenerTest : AbstractKoinTest() { } private fun createClient(player: Player) = - Client(pluginId, player.uniqueId, CoroutineScope(Dispatchers.Default + SupervisorJob())) + Client(player.uniqueId, CoroutineScope(Dispatchers.Default + SupervisorJob())) } diff --git a/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt b/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt index 4f0c6449..6b136001 100644 --- a/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt @@ -169,5 +169,5 @@ class ClientManagerImplTest : AbstractKoinTest() { } private fun createClient(player: Player) = - Client(pluginId, player.uniqueId, CoroutineScope(Dispatchers.Default + SupervisorJob())) + Client(player.uniqueId, CoroutineScope(Dispatchers.Default + SupervisorJob())) } diff --git a/src/test/kotlin/com/github/rushyverse/api/player/ClientTest.kt b/src/test/kotlin/com/github/rushyverse/api/player/ClientTest.kt index 92b8b5ba..89ac4179 100644 --- a/src/test/kotlin/com/github/rushyverse/api/player/ClientTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/player/ClientTest.kt @@ -17,7 +17,7 @@ class ClientTest : AbstractKoinTest() { @Test fun `retrieve player instance not found returns null`() { - val client = Client(pluginId, UUID.randomUUID(), CoroutineScope(EmptyCoroutineContext)) + val client = Client(UUID.randomUUID(), CoroutineScope(EmptyCoroutineContext)) every { server.getPlayer(any()) } returns null assertNull(client.player) } @@ -25,7 +25,7 @@ class ClientTest : AbstractKoinTest() { @Test fun `retrieve player instance found returns the instance`() { val uuid = UUID.randomUUID() - val client = Client(pluginId, uuid, CoroutineScope(EmptyCoroutineContext)) + val client = Client(uuid, CoroutineScope(EmptyCoroutineContext)) val player = mockk() every { server.getPlayer(uuid) } returns player @@ -34,7 +34,7 @@ class ClientTest : AbstractKoinTest() { @Test fun `require player instance not found throws an exception`() { - val client = Client(pluginId, UUID.randomUUID(), CoroutineScope(EmptyCoroutineContext)) + val client = Client(UUID.randomUUID(), CoroutineScope(EmptyCoroutineContext)) every { server.getPlayer(any()) } returns null assertThrows { client.requirePlayer() @@ -44,7 +44,7 @@ class ClientTest : AbstractKoinTest() { @Test fun `require player instance found returns the instance`() { val uuid = UUID.randomUUID() - val client = Client(pluginId, uuid, CoroutineScope(EmptyCoroutineContext)) + val client = Client(uuid, CoroutineScope(EmptyCoroutineContext)) val player = mockk() every { server.getPlayer(uuid) } returns player From 257fac8d4a066597dc7a8b804d5bc71a1e25b3d5 Mon Sep 17 00:00:00 2001 From: Distractic Date: Fri, 28 Jul 2023 22:21:12 +0200 Subject: [PATCH 081/143] chore: format --- .../rushyverse/api/configuration/reader/YamlFileReader.kt | 2 +- .../com/github/rushyverse/api/serializer/LocationSerializer.kt | 2 +- src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReader.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReader.kt index 0bcbf402..36dad032 100644 --- a/src/main/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReader.kt +++ b/src/main/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReader.kt @@ -50,7 +50,7 @@ public class YamlFileReader( * @param resourceFile The resource file to copy if the configuration file does not exist. */ private fun createConfigurationFileIfNecessary(config: File, resourceFile: String) { - if(config.exists()) return + if (config.exists()) return config.toPath().createParentDirectories() require(config.createNewFile()) { diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/LocationSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/LocationSerializer.kt index 43c47da7..ef5cffa3 100644 --- a/src/main/kotlin/com/github/rushyverse/api/serializer/LocationSerializer.kt +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/LocationSerializer.kt @@ -91,7 +91,7 @@ public object LocationSerializer : KSerializer { } Location( - world?.let { Bukkit.getServer().getWorld(it) }, + world?.let { Bukkit.getWorld(it) }, x ?: throw SerializationException("The field x is missing"), y ?: throw SerializationException("The field y is missing"), z ?: throw SerializationException("The field z is missing"), diff --git a/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt b/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt index f068320e..40de1ba7 100644 --- a/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt +++ b/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt @@ -14,7 +14,7 @@ import org.bukkit.Location /** * Serializer class for [CubeArea] objects. */ -public object CubeAreaSerializer: KSerializer { +public object CubeAreaSerializer : KSerializer { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("cubeArea") { val locationDescriptor = LocationSerializer.descriptor From 6cc7e8e9fb1ca1e63583a05d18eb1ac8c6b3caec Mon Sep 17 00:00:00 2001 From: Distractic Date: Fri, 28 Jul 2023 22:27:05 +0200 Subject: [PATCH 082/143] tests: Fix tests about get player from client --- .../rushyverse/api/player/ClientTest.kt | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/test/kotlin/com/github/rushyverse/api/player/ClientTest.kt b/src/test/kotlin/com/github/rushyverse/api/player/ClientTest.kt index 89ac4179..1732b797 100644 --- a/src/test/kotlin/com/github/rushyverse/api/player/ClientTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/player/ClientTest.kt @@ -1,5 +1,8 @@ package com.github.rushyverse.api.player +import be.seeseemelk.mockbukkit.MockBukkit +import be.seeseemelk.mockbukkit.ServerMock +import be.seeseemelk.mockbukkit.entity.PlayerMock import com.github.rushyverse.api.AbstractKoinTest import com.github.rushyverse.api.player.exception.PlayerNotFoundException import io.mockk.every @@ -9,33 +12,42 @@ import org.bukkit.entity.Player import org.junit.jupiter.api.assertThrows import java.util.* import kotlin.coroutines.EmptyCoroutineContext -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNull +import kotlin.test.* class ClientTest : AbstractKoinTest() { + private lateinit var player: PlayerMock + private lateinit var serverMock: ServerMock + + @BeforeTest + override fun onBefore() { + super.onBefore() + serverMock = MockBukkit.mock().apply { + player = addPlayer() + } + } + + @AfterTest + override fun onAfter() { + MockBukkit.unmock() + super.onAfter() + } + @Test fun `retrieve player instance not found returns null`() { val client = Client(UUID.randomUUID(), CoroutineScope(EmptyCoroutineContext)) - every { server.getPlayer(any()) } returns null assertNull(client.player) } @Test fun `retrieve player instance found returns the instance`() { - val uuid = UUID.randomUUID() - val client = Client(uuid, CoroutineScope(EmptyCoroutineContext)) - - val player = mockk() - every { server.getPlayer(uuid) } returns player + val client = Client(player.uniqueId, CoroutineScope(EmptyCoroutineContext)) assertEquals(player, client.player) } @Test fun `require player instance not found throws an exception`() { val client = Client(UUID.randomUUID(), CoroutineScope(EmptyCoroutineContext)) - every { server.getPlayer(any()) } returns null assertThrows { client.requirePlayer() } @@ -43,11 +55,7 @@ class ClientTest : AbstractKoinTest() { @Test fun `require player instance found returns the instance`() { - val uuid = UUID.randomUUID() - val client = Client(uuid, CoroutineScope(EmptyCoroutineContext)) - - val player = mockk() - every { server.getPlayer(uuid) } returns player + val client = Client(player.uniqueId, CoroutineScope(EmptyCoroutineContext)) assertEquals(player, client.requirePlayer()) } } From d5675e764b39c04c55d3c152fb47225151a8d387 Mon Sep 17 00:00:00 2001 From: Distractic Date: Fri, 28 Jul 2023 23:10:50 +0200 Subject: [PATCH 083/143] tests: Define custom koin context --- .../rushyverse/api/player/ClientManagerImplTest.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt b/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt index 6b136001..40bb67ed 100644 --- a/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt @@ -1,6 +1,8 @@ package com.github.rushyverse.api.player +import com.github.rushyverse.api.APIPlugin import com.github.rushyverse.api.AbstractKoinTest +import com.github.rushyverse.api.koin.CraftContext import com.github.rushyverse.api.player.exception.ClientNotFoundException import com.github.rushyverse.api.utils.randomString import io.mockk.every @@ -16,16 +18,21 @@ import org.junit.jupiter.api.assertThrows import java.util.* import kotlin.test.* -class ClientManagerImplTest : AbstractKoinTest() { +class ClientManagerImplTest { private lateinit var clientManager: ClientManager @BeforeTest - override fun onBefore() { - super.onBefore() + fun onBefore() { + CraftContext.startKoin(APIPlugin.ID_API) { } clientManager = ClientManagerImpl() } + @AfterTest + fun onAfter() { + CraftContext.stopKoin(APIPlugin.ID_API) + } + @Test fun `has no clients when created`() { assertEquals(0, clientManager.clients.size) From 555afb2af0d193dbbde5e35e08f2597a68bbdd96 Mon Sep 17 00:00:00 2001 From: Distractic Date: Fri, 28 Jul 2023 23:42:19 +0200 Subject: [PATCH 084/143] tests: Change koin use --- .../github/rushyverse/api/AbstractKoinTest.kt | 18 ++++-------------- .../api/player/ClientManagerImplTest.kt | 13 +++---------- .../github/rushyverse/api/player/ClientTest.kt | 3 --- 3 files changed, 7 insertions(+), 27 deletions(-) diff --git a/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt b/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt index 133a072b..a3599ad8 100644 --- a/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt @@ -5,7 +5,6 @@ import com.github.rushyverse.api.koin.loadModule import com.github.rushyverse.api.utils.randomString import io.mockk.every import io.mockk.mockk -import org.bukkit.Server import org.koin.core.module.Module import org.koin.dsl.ModuleDeclaration import kotlin.test.AfterTest @@ -15,9 +14,7 @@ abstract class AbstractKoinTest { lateinit var plugin: Plugin - lateinit var server: Server - - lateinit var pluginId: String + private lateinit var pluginId: String @BeforeTest open fun onBefore() { @@ -26,17 +23,12 @@ abstract class AbstractKoinTest { CraftContext.startKoin(APIPlugin.ID_API) { } loadTestModule { - plugin = mockk(randomString()) { + plugin = mockk { every { id } returns pluginId every { name } returns randomString() } single { plugin } } - - server = mockk(randomString()) - loadApiTestModule { - single { server } - } } @AfterTest @@ -45,12 +37,10 @@ abstract class AbstractKoinTest { CraftContext.stopKoin(APIPlugin.ID_API) } - inline fun testInject(): T = CraftContext.get(pluginId).inject().value - fun loadTestModule(moduleDeclaration: ModuleDeclaration): Module = - loadModule(pluginId, false, moduleDeclaration) + loadModule(pluginId, moduleDeclaration = moduleDeclaration) fun loadApiTestModule(moduleDeclaration: ModuleDeclaration): Module = - loadModule(APIPlugin.ID_API, true, moduleDeclaration) + loadModule(APIPlugin.ID_API, moduleDeclaration = moduleDeclaration) } diff --git a/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt b/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt index 40bb67ed..0d639394 100644 --- a/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt @@ -1,8 +1,6 @@ package com.github.rushyverse.api.player -import com.github.rushyverse.api.APIPlugin import com.github.rushyverse.api.AbstractKoinTest -import com.github.rushyverse.api.koin.CraftContext import com.github.rushyverse.api.player.exception.ClientNotFoundException import com.github.rushyverse.api.utils.randomString import io.mockk.every @@ -18,21 +16,16 @@ import org.junit.jupiter.api.assertThrows import java.util.* import kotlin.test.* -class ClientManagerImplTest { +class ClientManagerImplTest: AbstractKoinTest() { private lateinit var clientManager: ClientManager @BeforeTest - fun onBefore() { - CraftContext.startKoin(APIPlugin.ID_API) { } + override fun onBefore() { + super.onBefore() clientManager = ClientManagerImpl() } - @AfterTest - fun onAfter() { - CraftContext.stopKoin(APIPlugin.ID_API) - } - @Test fun `has no clients when created`() { assertEquals(0, clientManager.clients.size) diff --git a/src/test/kotlin/com/github/rushyverse/api/player/ClientTest.kt b/src/test/kotlin/com/github/rushyverse/api/player/ClientTest.kt index 1732b797..554a6237 100644 --- a/src/test/kotlin/com/github/rushyverse/api/player/ClientTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/player/ClientTest.kt @@ -5,10 +5,7 @@ import be.seeseemelk.mockbukkit.ServerMock import be.seeseemelk.mockbukkit.entity.PlayerMock import com.github.rushyverse.api.AbstractKoinTest import com.github.rushyverse.api.player.exception.PlayerNotFoundException -import io.mockk.every -import io.mockk.mockk import kotlinx.coroutines.CoroutineScope -import org.bukkit.entity.Player import org.junit.jupiter.api.assertThrows import java.util.* import kotlin.coroutines.EmptyCoroutineContext From a0e51e8c9fe1dd175ccefa5a53f38495c18a9414 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 29 Jul 2023 00:26:24 +0200 Subject: [PATCH 085/143] tests: Add debug for CI --- .../rushyverse/api/configuration/reader/YamlFileReaderTest.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt b/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt index f105e718..5a271668 100644 --- a/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt @@ -101,9 +101,11 @@ class YamlFileReaderTest { every { plugin.dataFolder } returns tempDir val expectedValue = TestValue(value) + println("Load config : ${load(configFile)}") load(configFile) shouldBe expectedValue val file = File(tempDir, configFile) + println("File [${file.absolutePath}] exists : ${file.exists()}") file.exists() shouldBe true file.readText() shouldBe "test: $value\r\n" } From 6b86375216c9316dc84807723fd523299cd5f608 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 29 Jul 2023 00:31:14 +0200 Subject: [PATCH 086/143] tests: Add debug for CI --- .github/workflows/Check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Check.yml b/.github/workflows/Check.yml index f128840e..5f41cea9 100644 --- a/.github/workflows/Check.yml +++ b/.github/workflows/Check.yml @@ -29,4 +29,4 @@ jobs: - name: Test uses: gradle/gradle-build-action@v2.4.0 with: - arguments: test + arguments: test --debug From 5bd57a4ec08538b324d4c113985ed964154461b3 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 29 Jul 2023 00:37:40 +0200 Subject: [PATCH 087/143] tests: Change line separator --- .../rushyverse/api/configuration/reader/YamlFileReaderTest.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt b/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt index 5a271668..78a53aa4 100644 --- a/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt @@ -101,13 +101,12 @@ class YamlFileReaderTest { every { plugin.dataFolder } returns tempDir val expectedValue = TestValue(value) - println("Load config : ${load(configFile)}") load(configFile) shouldBe expectedValue val file = File(tempDir, configFile) println("File [${file.absolutePath}] exists : ${file.exists()}") file.exists() shouldBe true - file.readText() shouldBe "test: $value\r\n" + file.readText() shouldBe "test: $value${System.lineSeparator()}" } fun shouldNotCreateFileIfExistsAndReadIt( From de2f4a8a64dde54169fb3e69ca52fb2d04798149 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 29 Jul 2023 00:40:24 +0200 Subject: [PATCH 088/143] tests: Remove debug --- .github/workflows/Check.yml | 2 +- .../rushyverse/api/configuration/reader/YamlFileReaderTest.kt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/Check.yml b/.github/workflows/Check.yml index 5f41cea9..f128840e 100644 --- a/.github/workflows/Check.yml +++ b/.github/workflows/Check.yml @@ -29,4 +29,4 @@ jobs: - name: Test uses: gradle/gradle-build-action@v2.4.0 with: - arguments: test --debug + arguments: test diff --git a/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt b/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt index 78a53aa4..44ba723d 100644 --- a/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt @@ -104,7 +104,6 @@ class YamlFileReaderTest { load(configFile) shouldBe expectedValue val file = File(tempDir, configFile) - println("File [${file.absolutePath}] exists : ${file.exists()}") file.exists() shouldBe true file.readText() shouldBe "test: $value${System.lineSeparator()}" } From d7b6391ee57ef07f8267dc4866ddb1d5e4cf53ae Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 29 Jul 2023 08:50:01 +0200 Subject: [PATCH 089/143] chore: Remove unused code --- .../rushyverse/api/extension/_Location.kt | 57 +++---------------- 1 file changed, 7 insertions(+), 50 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Location.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Location.kt index 82cb9ffe..c9a2fc40 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_Location.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Location.kt @@ -3,56 +3,6 @@ package com.github.rushyverse.api.extension import org.bukkit.Location import org.bukkit.World -/** - * Get the [Location.world] value from the location. - * @receiver Location. - * @return The world where is the location. - */ -public operator fun Location.component1(): World = world - -/** - * Get the [Location.x] value from the location. - * @receiver Location. - * @return Value of the coordinate x. - */ -public operator fun Location.component2(): Double = x - -/** - * Get the [Location.y] value from the location. - * @receiver Location. - * @return Value of the coordinate y. - */ -public operator fun Location.component3(): Double = y - -/** - * Get the [Location.z] value from the location. - * @receiver Location. - * @return Value of the coordinate z. - */ -public operator fun Location.component4(): Double = z - -/** - * Get the [Location.yaw] value from the location. - * @receiver Location. - * @return Value of the coordinate yaw. - */ -public operator fun Location.component5(): Float = yaw - -/** - * Get the [Location.pitch] value from the location. - * @receiver Location. - * @return Value of the coordinate pitch. - */ -public operator fun Location.component6(): Float = pitch - -/** - * Create a new location with the coordinate center to the current block. - * Define the x and z properties to the block location + 0.5. - * @receiver Location. - * @return New location corresponding to the current location center on the block. - */ -public fun Location.center(): Location = copy(x = blockX + 0.5, z = blockZ + 0.5) - /** * Define the position (x, y, z, pitch, yaw) and the world with the same value as the location in parameter. * @receiver Location that will have its values modified @@ -87,6 +37,13 @@ public fun Location.copy( pitch: Float = this.pitch, ): Location = Location(world, x, y, z, yaw, pitch) +/** + * Divides each coordinate of the current location by the given value. + * + * @receiver The current location. + * @param value The value to divide each coordinate by. + * @return A new location with the divided coordinates. + */ public fun Location.divide(value: Number): Location { val toDouble = value.toDouble() return copy(x = x / toDouble, y = y / toDouble, z = z / toDouble) From b6ea21dc6820db6a2294c1d16af079b6a9a6c187 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 29 Jul 2023 08:50:25 +0200 Subject: [PATCH 090/143] tests: Remove unused code --- .../reader/YamlFileReaderTest.kt | 6 +-- .../api/extension/LocationExtTest.kt | 44 ------------------- .../github/rushyverse/api/utils/Generator.kt | 2 +- 3 files changed, 4 insertions(+), 48 deletions(-) diff --git a/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt b/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt index 44ba723d..7bc41fd6 100644 --- a/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt @@ -56,7 +56,7 @@ class YamlFileReaderTest { @Test fun `should not create file if exists and read it`() { - shouldNotCreateFileIfExistsAndReadIt { reader.readConfigurationFile(TestValue::class, it) } + shouldNotCreateFileIfExistsAndDecode { reader.readConfigurationFile(TestValue::class, it) } } } @@ -89,7 +89,7 @@ class YamlFileReaderTest { @Test fun `should not create file if exists and read it`() { - shouldNotCreateFileIfExistsAndReadIt { reader.readConfigurationFile(TestValue.serializer(), it) } + shouldNotCreateFileIfExistsAndDecode { reader.readConfigurationFile(TestValue.serializer(), it) } } } @@ -108,7 +108,7 @@ class YamlFileReaderTest { file.readText() shouldBe "test: $value${System.lineSeparator()}" } - fun shouldNotCreateFileIfExistsAndReadIt( + fun shouldNotCreateFileIfExistsAndDecode( load: (String) -> TestValue ) { every { plugin.dataFolder } returns tempDir diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/LocationExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/LocationExtTest.kt index a2e74020..1f900e7b 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/LocationExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/LocationExtTest.kt @@ -20,50 +20,6 @@ class LocationExtTest { loc = Location(mockk(randomString()), 0.0, 1.0, 2.0, 3.0f, 4.0f) } - @Test - fun `center the location`() { - val expectedX = loc.blockX + 0.5 - val expectedZ = loc.blockZ + 0.5 - val locCenter = loc.center() - assertEquals(expectedX, locCenter.x) - assertEquals(expectedZ, locCenter.z) - } - - @Nested - @DisplayName("Components") - inner class Components { - - @Test - fun `component1 give the world property`() { - assertEquals(loc.world, loc.component1()) - } - - @Test - fun `component2 give the x property`() { - assertEquals(loc.x, loc.component2()) - } - - @Test - fun `component3 give the y property`() { - assertEquals(loc.y, loc.component3()) - } - - @Test - fun `component4 give the z property`() { - assertEquals(loc.z, loc.component4()) - } - - @Test - fun `component5 give the yaw property`() { - assertEquals(loc.yaw, loc.component5()) - } - - @Test - fun `component6 give the pitch property`() { - assertEquals(loc.pitch, loc.component6()) - } - } - @Nested @DisplayName("Copy properties") inner class Copy { diff --git a/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt b/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt index 1957d5cc..0d378edf 100644 --- a/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt +++ b/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt @@ -15,7 +15,7 @@ fun randomInt() = Random.nextInt() fun randomLong() = Random.nextLong() -fun randomFloat() = Random.nextFloat() +fun randomFloat() = randomDouble().toFloat() fun randomDouble() = Random.nextDouble() From 3351e861ea3abcd5ac439ac6bb46f4ede98c0ff4 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 29 Jul 2023 08:50:38 +0200 Subject: [PATCH 091/143] chore: Use generic plugin --- .../kotlin/com/github/rushyverse/api/extension/_Runnable.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Runnable.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Runnable.kt index f260b6b9..9da15608 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_Runnable.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Runnable.kt @@ -1,10 +1,10 @@ package com.github.rushyverse.api.extension +import com.github.shynixn.mccoroutine.bukkit.SuspendingPlugin import com.github.shynixn.mccoroutine.bukkit.asyncDispatcher import com.github.shynixn.mccoroutine.bukkit.minecraftDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.withContext -import org.bukkit.plugin.Plugin import org.bukkit.scheduler.BukkitRunnable /** @@ -27,7 +27,7 @@ public inline fun BukkitRunnable(crossinline task: BukkitRunnable.() -> Unit): B * @return T Instance returned after the execution of the task. */ public suspend inline fun onPrimaryThread( - plugin: Plugin, + plugin: SuspendingPlugin, noinline block: suspend CoroutineScope.() -> T ): T = withContext(plugin.minecraftDispatcher, block) @@ -38,6 +38,6 @@ public suspend inline fun onPrimaryThread( * @return T Instance returned after the execution of the task. */ public suspend inline fun onAsyncThread( - plugin: Plugin, + plugin: SuspendingPlugin, noinline block: suspend CoroutineScope.() -> T ): T = withContext(plugin.asyncDispatcher, block) From 3d5ebb0d2a01aeda89bf733007fa109ca78989c7 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 29 Jul 2023 08:51:43 +0200 Subject: [PATCH 092/143] chore: Extract area interface --- .../com/github/rushyverse/api/world/Area.kt | 29 +++++++++++++++++++ .../github/rushyverse/api/world/CubeArea.kt | 13 ++------- 2 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 src/main/kotlin/com/github/rushyverse/api/world/Area.kt diff --git a/src/main/kotlin/com/github/rushyverse/api/world/Area.kt b/src/main/kotlin/com/github/rushyverse/api/world/Area.kt new file mode 100644 index 00000000..2f7d2247 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/world/Area.kt @@ -0,0 +1,29 @@ +package com.github.rushyverse.api.world + +import org.bukkit.Location + +/** + * Checks if the given Location is within the specified Area. + * + * @receiver The Location object. + * @param area The Area to check against. + * @return true if the Location is within the Area, false otherwise. + */ +public infix fun Location.isIn(area: Area): Boolean = area.isInArea(this) + +/** + * Represents an area in the world. + * @property location The location of the area. + */ +public interface Area { + + public var location: Location + + /** + * Determines if a given location is within the specified area. + * + * @param location The location to check. + * @return `true` if the location is within the area, `false` otherwise. + */ + public fun isInArea(location: Location): Boolean +} diff --git a/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt b/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt index 40de1ba7..ff278fa0 100644 --- a/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt +++ b/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt @@ -65,12 +65,11 @@ public object CubeAreaSerializer : KSerializer { * A cuboid area defined by two positions. * @property min Minimum position. * @property max Maximum position. - * @property center Center position of the cube. */ @Serializable(with = CubeAreaSerializer::class) -public class CubeArea(loc1: Location, loc2: Location) { +public class CubeArea(loc1: Location, loc2: Location): Area { - public var center: Location + public override var location: Location get() = max.centerRelative(min) set(value) { // The new position becomes the center of the cube. @@ -98,13 +97,7 @@ public class CubeArea(loc1: Location, loc2: Location) { this.max = Location(world2, x2, y2, z2) } - /** - * Determines if a given location is within the specified area. - * - * @param location The location to check. - * @return True if the location is within the area, false otherwise. - */ - public fun isInArea(location: Location): Boolean { + public override fun isInArea(location: Location): Boolean { return min.world === location.world && location.x in min.x..max.x && location.y in min.y..max.y && From d4d39ab125845214c026066ba732b1fedd2827bb Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 29 Jul 2023 08:52:00 +0200 Subject: [PATCH 093/143] feat(area): Add cylinder area --- .../rushyverse/api/world/CylinderArea.kt | 37 +++++ .../rushyverse/api/world/CylinderAreaTest.kt | 157 ++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 src/main/kotlin/com/github/rushyverse/api/world/CylinderArea.kt create mode 100644 src/test/kotlin/com/github/rushyverse/api/world/CylinderAreaTest.kt diff --git a/src/main/kotlin/com/github/rushyverse/api/world/CylinderArea.kt b/src/main/kotlin/com/github/rushyverse/api/world/CylinderArea.kt new file mode 100644 index 00000000..f9d6de23 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/world/CylinderArea.kt @@ -0,0 +1,37 @@ +package com.github.rushyverse.api.world + +import org.bukkit.Location +import kotlin.math.pow +import kotlin.math.sqrt + +public class CylinderArea( + override var location: Location, + radius: Double, + public val limitY: ClosedRange, +) : Area { + + public var radius: Double = radius + set(value) { + verifyNewRadiusValue(value) + field = value + } + + init { + verifyNewRadiusValue(radius) + } + + /** + * Verifies that the new radius value is greater than or equal to 0.0. + * @param value New radius value. + */ + private fun verifyNewRadiusValue(value: Double) { + require(value >= 0.0) { "Radius must be greater than or equal to 0.0" } + } + + override fun isInArea(location: Location): Boolean { + val areaLocation = this.location + return location.world === areaLocation.world // Same world + && sqrt((location.x - areaLocation.x).pow(2.0) + (location.z - areaLocation.z).pow(2.0)) <= radius // Within radius + && location.y in limitY // Within height + } +} diff --git a/src/test/kotlin/com/github/rushyverse/api/world/CylinderAreaTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/CylinderAreaTest.kt new file mode 100644 index 00000000..45876634 --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/world/CylinderAreaTest.kt @@ -0,0 +1,157 @@ +package com.github.rushyverse.api.world + +import be.seeseemelk.mockbukkit.MockBukkit +import be.seeseemelk.mockbukkit.ServerMock +import be.seeseemelk.mockbukkit.WorldMock +import com.github.rushyverse.api.extension.copy +import com.github.rushyverse.api.utils.randomString +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import org.bukkit.Location +import org.bukkit.entity.Player +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertContentEquals + +class CylinderAreaTest { + + private lateinit var serverMock: ServerMock + private lateinit var worldMock: WorldMock + + @BeforeTest + fun onBefore() { + serverMock = MockBukkit.mock() + worldMock = serverMock.addSimpleWorld("world") + } + + @AfterTest + fun onAfter() { + MockBukkit.unmock() + } + + @Nested + inner class Instantiation { + + @Test + fun `should throw an exception if the radius is negative`() { + shouldThrow { + CylinderArea(mockk(), -1.0, 0.0..0.0) + } + } + + @Test + fun `should throw an exception if the radius is set`() { + val area = CylinderArea(mockk(), 0.0, 0.0..0.0) + assertThrows { + area.radius = -1.0 + } + } + + @Test + fun `should set the radius without exception if value is zero or positive`() { + val area = CylinderArea(mockk(), 0.0, 0.0..0.0) + + area.radius = 0.0 + area.radius shouldBe 0.0 + + area.radius = 1.0 + area.radius shouldBe 1.0 + } + } + + @Nested + inner class UpdateWithYChange { + + @Test + fun `should use negative y limit`() { + val min = Location(worldMock, 0.0, 0.0, 0.0) + val area = CylinderArea(min, 1.0, -10.0..-5.0) + + min.copy(y = -5.0) isIn area shouldBe true + min.copy(y = -8.1) isIn area shouldBe true + min.copy(y = -11.0) isIn area shouldBe false + min.copy(y = -4.9) isIn area shouldBe false + } + + @Test + fun `should use positive y limit`() { + val min = Location(worldMock, 0.0, 0.0, 0.0) + val area = CylinderArea(min, 0.0, 5.0..10.0) + + min.copy(y = 5.0) isIn area shouldBe true + min.copy(y = 7.3) isIn area shouldBe true + min.copy(y = 4.3) isIn area shouldBe false + min.copy(y = 10.1) isIn area shouldBe false + } + + @Test + fun `should use negative and positive y limit`() { + val min = Location(worldMock, 0.0, 0.0, 0.0) + val area = CylinderArea(min, 0.0, -5.0..10.0) + + min.copy(y = 0.0) isIn area shouldBe true + min.copy(y = -3.0) isIn area shouldBe true + min.copy(y = 8.0) isIn area shouldBe true + min.copy(y = 10.1) isIn area shouldBe false + min.copy(y = -5.1) isIn area shouldBe false + } + + @Test + fun `should use zero y limit`() { + val min = Location(worldMock, 0.0, 0.0, 0.0) + val area = CylinderArea(min, 0.0, 0.0..0.0) + + min.copy(y = 0.0) isIn area shouldBe true + min.copy(y = -0.1) isIn area shouldBe false + min.copy(y = 0.1) isIn area shouldBe false + } + + } + + @Nested + inner class UpdateWithRadiusChange { + + @Test + fun `should use zero for radius`() { + val min = Location(worldMock, 0.0, 0.0, 0.0) + val area = CylinderArea(min, 0.0, 0.0..0.0) + + min.copy(x = 0.0, z = 0.0) isIn area shouldBe true + min.copy(x = 0.1, z = 0.0) isIn area shouldBe false + min.copy(x = 0.0, z = 0.1) isIn area shouldBe false + min.copy(x = -0.1, z = 0.0) isIn area shouldBe false + min.copy(x = 0.0, z = -0.1) isIn area shouldBe false + } + + @Test + fun `should use positive for radius`() { + val min = Location(worldMock, 0.0, 0.0, 0.0) + val area = CylinderArea(min, 1.0, 0.0..0.0) + + min.copy(x = 0.0, z = 0.0) isIn area shouldBe true + min.copy(x = 1.0, z = 0.0) isIn area shouldBe true + min.copy(x = 0.0, z = 1.0) isIn area shouldBe true + min.copy(x = -1.0, z = 0.0) isIn area shouldBe true + min.copy(x = 0.0, z = -1.0) isIn area shouldBe true + min.copy(x = 1.1, z = 0.0) isIn area shouldBe false + min.copy(x = 0.0, z = 1.1) isIn area shouldBe false + min.copy(x = -1.1, z = 0.0) isIn area shouldBe false + min.copy(x = 0.0, z = -1.1) isIn area shouldBe false + + min.copy(x = 1.0, z = 0.1) isIn area shouldBe false + min.copy(x = -1.0, z = 0.1) isIn area shouldBe false + min.copy(x = 1.0, z = -0.1) isIn area shouldBe false + min.copy(x = -1.0, z = -0.1) isIn area shouldBe false + + min.copy(x = 0.1, z = 1.0) isIn area shouldBe false + min.copy(x = -0.1, z = 1.0) isIn area shouldBe false + min.copy(x = 0.1, z = -1.0) isIn area shouldBe false + min.copy(x = -0.1, z = -1.0) isIn area shouldBe false + } + } +} From f070c0975c6d396247c54562084928e232cbf09c Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 29 Jul 2023 15:08:35 +0200 Subject: [PATCH 094/143] chore: Change cube serializer --- .../kotlin/com/github/rushyverse/api/world/CubeArea.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt b/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt index ff278fa0..e9c9a16b 100644 --- a/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt +++ b/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt @@ -16,10 +16,10 @@ import org.bukkit.Location */ public object CubeAreaSerializer : KSerializer { - override val descriptor: SerialDescriptor = buildClassSerialDescriptor("cubeArea") { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("cube") { val locationDescriptor = LocationSerializer.descriptor - element("loc1", locationDescriptor) - element("loc2", locationDescriptor) + element("location1", locationDescriptor) + element("location2", locationDescriptor) } override fun deserialize(decoder: Decoder): CubeArea { @@ -44,8 +44,8 @@ public object CubeAreaSerializer : KSerializer { } CubeArea( - loc1 ?: throw SerializationException("The field loc1 is missing"), - loc2 ?: throw SerializationException("The field loc2 is missing"), + loc1 ?: throw SerializationException("The field location1 is missing"), + loc2 ?: throw SerializationException("The field location2 is missing"), ) } } From 90c56ca8cf6df67fcd85254caa9f1eb34fc3d9e7 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 29 Jul 2023 15:08:59 +0200 Subject: [PATCH 095/143] feat: Add cylinder serializer --- .../api/serializer/RangeDoubleSerializer.kt | 63 +++++++++++++ .../rushyverse/api/world/CylinderArea.kt | 94 ++++++++++++++++++- 2 files changed, 152 insertions(+), 5 deletions(-) create mode 100644 src/main/kotlin/com/github/rushyverse/api/serializer/RangeDoubleSerializer.kt diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/RangeDoubleSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/RangeDoubleSerializer.kt new file mode 100644 index 00000000..83ad753d --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/RangeDoubleSerializer.kt @@ -0,0 +1,63 @@ +package com.github.rushyverse.api.serializer + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.* + +/** + * Serializer for [ClosedRange] with [Double] value. + */ +public object RangeDoubleSerializer : KSerializer> { + + /** + * Serializer for [Double]. + */ + private val doubleSerializer get() = Double.serializer() + + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("range") { + val doubleSerializer = doubleSerializer + + element("start", doubleSerializer.descriptor) + element("end", doubleSerializer.descriptor) + } + + override fun serialize(encoder: Encoder, value: ClosedRange) { + val doubleSerializer = doubleSerializer + + encoder.encodeStructure(descriptor) { + encodeSerializableElement(descriptor, 0, doubleSerializer, value.start) + encodeSerializableElement(descriptor, 1, doubleSerializer, value.endInclusive) + } + } + + override fun deserialize(decoder: Decoder): ClosedRange { + val doubleSerializer = doubleSerializer + + return decoder.decodeStructure(descriptor) { + var start: Double? = null + var end: Double? = null + + if (decodeSequentially()) { + start = decodeSerializableElement(descriptor, 0, doubleSerializer) + end = decodeSerializableElement(descriptor, 1, doubleSerializer) + } else { + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> start = decodeSerializableElement(descriptor, index, doubleSerializer) + 1 -> end = decodeSerializableElement(descriptor, index, doubleSerializer) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + } + + start ?: throw SerializationException("The field start is missing") + end ?: throw SerializationException("The field end is missing") + + start..end + } + } +} diff --git a/src/main/kotlin/com/github/rushyverse/api/world/CylinderArea.kt b/src/main/kotlin/com/github/rushyverse/api/world/CylinderArea.kt index f9d6de23..024ece8c 100644 --- a/src/main/kotlin/com/github/rushyverse/api/world/CylinderArea.kt +++ b/src/main/kotlin/com/github/rushyverse/api/world/CylinderArea.kt @@ -1,30 +1,114 @@ package com.github.rushyverse.api.world +import com.github.rushyverse.api.serializer.LocationSerializer +import com.github.rushyverse.api.serializer.RangeDoubleSerializer +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.* import org.bukkit.Location import kotlin.math.pow import kotlin.math.sqrt +/** + * Serializer class for [CylinderArea] objects. + */ +public object CylinderAreaSerializer : KSerializer { + + /** + * Serializer for [Location]. + */ + private val locationSerializer get() = LocationSerializer + + /** + * Serializer for [Double]. + */ + private val doubleSerializer get() = Double.serializer() + + /** + * Serializer for [ClosedRange] with [Double] value. + */ + private val rangeDoubleSerializer get() = RangeDoubleSerializer + + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("cylinder") { + element("location", locationSerializer.descriptor) + element("radius", doubleSerializer.descriptor) + element("height", rangeDoubleSerializer.descriptor) + } + + override fun deserialize(decoder: Decoder): CylinderArea { + return decoder.decodeStructure(descriptor) { + var location: Location? = null + var radius: Double? = null + var height: ClosedRange? = null + + if (decodeSequentially()) { + location = decodeSerializableElement(descriptor, 0, locationSerializer) + radius = decodeSerializableElement(descriptor, 1, doubleSerializer) + height = decodeSerializableElement(descriptor, 2, rangeDoubleSerializer) + } else { + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> location = decodeSerializableElement(descriptor, index, locationSerializer) + 1 -> radius = decodeSerializableElement(descriptor, index, doubleSerializer) + 2 -> height = decodeSerializableElement(descriptor, index, rangeDoubleSerializer) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + } + + CylinderArea( + location ?: throw SerializationException("The field location is missing"), + radius ?: throw SerializationException("The field radius is missing"), + height ?: throw SerializationException("The field height is missing"), + ) + } + } + + override fun serialize(encoder: Encoder, value: CylinderArea) { + encoder.encodeStructure(descriptor) { + encodeSerializableElement(descriptor, 0, locationSerializer, value.location) + encodeSerializableElement(descriptor, 1, doubleSerializer, value.radius) + encodeSerializableElement(descriptor, 2, rangeDoubleSerializer, value.height) + } + } + +} + +/** + * Represents a cylindrical area in a three-dimensional space. + * + * @property location The location of the center of the cylinder. + * @property radius The radius of the cylinder. Must be greater than or equal to 0.0. + * @property height The range of valid heights for the cylinder. + * @constructor Creates a CylinderArea object with the specified location, radius, and height range. + */ +@Serializable(with = CylinderAreaSerializer::class) public class CylinderArea( override var location: Location, radius: Double, - public val limitY: ClosedRange, + public val height: ClosedRange, ) : Area { public var radius: Double = radius set(value) { - verifyNewRadiusValue(value) + assertRadiusValue(value) field = value } init { - verifyNewRadiusValue(radius) + assertRadiusValue(radius) } /** * Verifies that the new radius value is greater than or equal to 0.0. * @param value New radius value. */ - private fun verifyNewRadiusValue(value: Double) { + private fun assertRadiusValue(value: Double) { require(value >= 0.0) { "Radius must be greater than or equal to 0.0" } } @@ -32,6 +116,6 @@ public class CylinderArea( val areaLocation = this.location return location.world === areaLocation.world // Same world && sqrt((location.x - areaLocation.x).pow(2.0) + (location.z - areaLocation.z).pow(2.0)) <= radius // Within radius - && location.y in limitY // Within height + && location.y in height // Within height } } From 55b869d3729b4a8f5442b5c010ad1dc2f87fe08f Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 29 Jul 2023 22:11:31 +0200 Subject: [PATCH 096/143] chore: Use variable instead of property --- src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt b/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt index e9c9a16b..bdeb97f7 100644 --- a/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt +++ b/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt @@ -67,7 +67,7 @@ public object CubeAreaSerializer : KSerializer { * @property max Maximum position. */ @Serializable(with = CubeAreaSerializer::class) -public class CubeArea(loc1: Location, loc2: Location): Area { +public class CubeArea(loc1: Location, loc2: Location) : Area { public override var location: Location get() = max.centerRelative(min) @@ -98,6 +98,8 @@ public class CubeArea(loc1: Location, loc2: Location): Area { } public override fun isInArea(location: Location): Boolean { + val min = min + val max = max return min.world === location.world && location.x in min.x..max.x && location.y in min.y..max.y && From 1f6385127ef8365385bd00b68eec59d06dcd4f87 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 29 Jul 2023 22:11:40 +0200 Subject: [PATCH 097/143] feat(area): Add sphere --- .../github/rushyverse/api/world/SphereArea.kt | 105 +++++++++++++++ .../rushyverse/api/world/SphereAreaTest.kt | 120 ++++++++++++++++++ 2 files changed, 225 insertions(+) create mode 100644 src/main/kotlin/com/github/rushyverse/api/world/SphereArea.kt create mode 100644 src/test/kotlin/com/github/rushyverse/api/world/SphereAreaTest.kt diff --git a/src/main/kotlin/com/github/rushyverse/api/world/SphereArea.kt b/src/main/kotlin/com/github/rushyverse/api/world/SphereArea.kt new file mode 100644 index 00000000..a62e4dc2 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/world/SphereArea.kt @@ -0,0 +1,105 @@ +package com.github.rushyverse.api.world + +import com.github.rushyverse.api.serializer.LocationSerializer +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.* +import org.bukkit.Location +import kotlin.math.pow + +/** + * Serializer class for [SphereArea] objects. + */ +public object SphereAreaSerializer : KSerializer { + + /** + * Serializer for [Location]. + */ + private val locationSerializer get() = LocationSerializer + + /** + * Serializer for [Double]. + */ + private val doubleSerializer get() = Double.serializer() + + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("sphere") { + element("location", locationSerializer.descriptor) + element("radius", doubleSerializer.descriptor) + } + + override fun deserialize(decoder: Decoder): SphereArea { + return decoder.decodeStructure(descriptor) { + var location: Location? = null + var radius: Double? = null + + if (decodeSequentially()) { + location = decodeSerializableElement(descriptor, 0, locationSerializer) + radius = decodeSerializableElement(descriptor, 1, doubleSerializer) + } else { + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> location = decodeSerializableElement(descriptor, index, locationSerializer) + 1 -> radius = decodeSerializableElement(descriptor, index, doubleSerializer) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + } + + SphereArea( + location ?: throw SerializationException("The field location is missing"), + radius ?: throw SerializationException("The field radius is missing"), + ) + } + } + + override fun serialize(encoder: Encoder, value: SphereArea) { + encoder.encodeStructure(descriptor) { + encodeSerializableElement(descriptor, 0, locationSerializer, value.location) + encodeSerializableElement(descriptor, 1, doubleSerializer, value.radius) + } + } + +} + +/** + * Represents a spherical area with a certain radius. + * + * @property location The center location of the sphere. + * @property radius The radius of the sphere. + */ +@Serializable(with = SphereAreaSerializer::class) +public class SphereArea( + override var location: Location, + radius: Double, +) : Area { + + public var radius: Double = radius + set(value) { + assertRadiusValue(value) + field = value + } + + init { + assertRadiusValue(radius) + } + + /** + * Verifies that the new radius value is greater than or equal to 0.0. + * @param value New radius value. + */ + private fun assertRadiusValue(value: Double) { + require(value >= 0.0) { "Radius must be greater than or equal to 0.0" } + } + + override fun isInArea(location: Location): Boolean { + println("Checking if $location is in ${this.location} : ${location.distanceSquared(this.location)}") + val areaLocation = this.location + return location.world === areaLocation.world // Same world + && location.distanceSquared(areaLocation) <= radius.pow(2) // Distance is less than or equal to radius + } +} diff --git a/src/test/kotlin/com/github/rushyverse/api/world/SphereAreaTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/SphereAreaTest.kt new file mode 100644 index 00000000..466175fa --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/world/SphereAreaTest.kt @@ -0,0 +1,120 @@ +package com.github.rushyverse.api.world + +import be.seeseemelk.mockbukkit.MockBukkit +import be.seeseemelk.mockbukkit.ServerMock +import be.seeseemelk.mockbukkit.WorldMock +import com.github.rushyverse.api.extension.copy +import com.github.rushyverse.api.utils.randomString +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import org.bukkit.Location +import org.bukkit.entity.Player +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import kotlin.math.floor +import kotlin.math.round +import kotlin.math.sqrt +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertContentEquals + +class SphereAreaTest { + + private lateinit var serverMock: ServerMock + private lateinit var worldMock: WorldMock + + @BeforeTest + fun onBefore() { + serverMock = MockBukkit.mock() + worldMock = serverMock.addSimpleWorld("world") + } + + @AfterTest + fun onAfter() { + MockBukkit.unmock() + } + + @Nested + inner class Instantiation { + + @Test + fun `should throw an exception if the radius is negative`() { + shouldThrow { + SphereArea(mockk(), -1.0) + } + } + + @Test + fun `should throw an exception if the radius is set`() { + val area = SphereArea(mockk(), 0.0) + assertThrows { + area.radius = -1.0 + } + } + + @Test + fun `should set the radius without exception if value is zero or positive`() { + val area = SphereArea(mockk(), 0.0) + + area.radius = 0.0 + area.radius shouldBe 0.0 + + area.radius = 1.0 + area.radius shouldBe 1.0 + } + } + + @Nested + inner class UpdateWithRadiusChange { + + @Test + fun `should use zero for radius`() { + val min = Location(worldMock, 0.0, 0.0, 0.0) + val area = SphereArea(min, 0.0) + + min.copy(x = 0.0, y = 0.0, z = 0.0) isIn area shouldBe true + + min.copy(x = 0.1, y = 0.0, z = 0.0) isIn area shouldBe false + min.copy(x = 0.0, y = 0.1, z = 0.0) isIn area shouldBe false + min.copy(x = 0.0, y = 0.0, z = 0.1) isIn area shouldBe false + min.copy(x = 0.1, y = 0.1, z = 0.1) isIn area shouldBe false + + min.copy(x = -0.1, y = 0.0, z = 0.0) isIn area shouldBe false + min.copy(x = 0.0, y = -0.1, z = 0.0) isIn area shouldBe false + min.copy(x = 0.0, y = 0.0, z = -0.1) isIn area shouldBe false + } + + @Test + fun `should use positive for radius`() { + val min = Location(worldMock, 0.0, 0.0, 0.0) + val area = SphereArea(min, 5.0) + println(sqrt(5.0 * 5.0 / 3.0)) + + min.copy(x = 0.0, y = 0.0, z = 0.0) isIn area shouldBe true + min.copy(x = 5.0, y = 0.0, z = 0.0) isIn area shouldBe true + min.copy(x = 0.0, y = 5.0, z = 0.0) isIn area shouldBe true + min.copy(x = 0.0, y = 0.0, z = 5.0) isIn area shouldBe true + min.copy(x = -5.0, y = 0.0, z = 0.0) isIn area shouldBe true + min.copy(x = 0.0, y = -5.0, z = 0.0) isIn area shouldBe true + min.copy(x = 0.0, y = 0.0, z = -5.0) isIn area shouldBe true + + min.copy(x = 5.1, y = 0.0, z = 0.0) isIn area shouldBe false + min.copy(x = 0.0, y = 5.1, z = 0.0) isIn area shouldBe false + min.copy(x = 0.0, y = 0.0, z = 5.1) isIn area shouldBe false + min.copy(x = -5.1, y = 0.0, z = 0.0) isIn area shouldBe false + min.copy(x = 0.0, y = -5.1, z = 0.0) isIn area shouldBe false + min.copy(x = 0.0, y = 0.0, z = -5.1) isIn area shouldBe false + + min.copy(x = 5.0, y = 5.0, z = 5.0) isIn area shouldBe false + min.copy(x = -5.0, y = -5.0, z = -5.0) isIn area shouldBe false + + val maxDiagonal = floor(sqrt(5.0 * 5.0 / 3.0) * 100) / 100 // The limit is 25 for radius 5.0 with x,y,z = ~2.886 + min.copy(x = maxDiagonal, y = maxDiagonal, z = maxDiagonal) isIn area shouldBe true + min.copy(x = -maxDiagonal, y = -maxDiagonal, z = -maxDiagonal) isIn area shouldBe true + min.copy(x = maxDiagonal + 0.1, y = maxDiagonal + 0.1, z = maxDiagonal + 0.1) isIn area shouldBe false + } + } +} From b64ae2d1e91f524ccb725d20d2f47146e6be1666 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 30 Jul 2023 12:29:17 +0200 Subject: [PATCH 098/143] chore: Define equals & hashcode --- .../github/rushyverse/api/world/CubeArea.kt | 17 ++++++++++++ .../rushyverse/api/world/CylinderArea.kt | 26 +++++++++++++++++++ .../github/rushyverse/api/world/SphereArea.kt | 24 +++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt b/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt index bdeb97f7..50e4ea1a 100644 --- a/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt +++ b/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt @@ -110,4 +110,21 @@ public class CubeArea(loc1: Location, loc2: Location) : Area { return "CubeArea(min=$min, max=$max)" } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as CubeArea + + if (min != other.min) return false + if (max != other.max) return false + + return true + } + + override fun hashCode(): Int { + var result = min.hashCode() + result = 31 * result + max.hashCode() + return result + } } diff --git a/src/main/kotlin/com/github/rushyverse/api/world/CylinderArea.kt b/src/main/kotlin/com/github/rushyverse/api/world/CylinderArea.kt index 024ece8c..1f05b4ec 100644 --- a/src/main/kotlin/com/github/rushyverse/api/world/CylinderArea.kt +++ b/src/main/kotlin/com/github/rushyverse/api/world/CylinderArea.kt @@ -118,4 +118,30 @@ public class CylinderArea( && sqrt((location.x - areaLocation.x).pow(2.0) + (location.z - areaLocation.z).pow(2.0)) <= radius // Within radius && location.y in height // Within height } + + override fun toString(): String { + return "CylinderArea(location=$location, height=$height, radius=$radius)" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as CylinderArea + + if (location != other.location) return false + if (height != other.height) return false + if (radius != other.radius) return false + + return true + } + + override fun hashCode(): Int { + var result = location.hashCode() + result = 31 * result + height.hashCode() + result = 31 * result + radius.hashCode() + return result + } + + } diff --git a/src/main/kotlin/com/github/rushyverse/api/world/SphereArea.kt b/src/main/kotlin/com/github/rushyverse/api/world/SphereArea.kt index a62e4dc2..0f5a9c2c 100644 --- a/src/main/kotlin/com/github/rushyverse/api/world/SphereArea.kt +++ b/src/main/kotlin/com/github/rushyverse/api/world/SphereArea.kt @@ -102,4 +102,28 @@ public class SphereArea( return location.world === areaLocation.world // Same world && location.distanceSquared(areaLocation) <= radius.pow(2) // Distance is less than or equal to radius } + + override fun toString(): String { + return "SphereArea(location=$location, radius=$radius)" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as SphereArea + + if (location != other.location) return false + if (radius != other.radius) return false + + return true + } + + override fun hashCode(): Int { + var result = location.hashCode() + result = 31 * result + radius.hashCode() + return result + } + + } From 1c98d37f5d437793d4dcb226a71df081adc022fb Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 30 Jul 2023 12:29:31 +0200 Subject: [PATCH 099/143] tests(area): Add sphere area test --- .../world/sphere/SphereAreaSerializerTest.kt | 147 ++++++++++++++++++ .../api/world/{ => sphere}/SphereAreaTest.kt | 9 +- 2 files changed, 150 insertions(+), 6 deletions(-) create mode 100644 src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaSerializerTest.kt rename src/test/kotlin/com/github/rushyverse/api/world/{ => sphere}/SphereAreaTest.kt (95%) diff --git a/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaSerializerTest.kt new file mode 100644 index 00000000..1bc31596 --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaSerializerTest.kt @@ -0,0 +1,147 @@ +package com.github.rushyverse.api.world.sphere + +import be.seeseemelk.mockbukkit.MockBukkit +import be.seeseemelk.mockbukkit.WorldMock +import com.github.rushyverse.api.utils.randomDouble +import com.github.rushyverse.api.utils.randomFloat +import com.github.rushyverse.api.world.SphereArea +import io.kotest.assertions.json.shouldEqualJson +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import kotlinx.serialization.json.Json +import org.bukkit.Location +import org.junit.jupiter.api.Nested +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test + +class SphereAreaSerializerTest { + + private lateinit var world: WorldMock + + @BeforeTest + fun onBefore() { + world = WorldMock() + MockBukkit.mock().apply { + addWorld(world) + } + } + + @AfterTest + fun onAfter() { + MockBukkit.unmock() + } + + @Nested + inner class Serialize { + + @Test + fun `should with only coordinate for location`() { + val loc = Location(null, randomDouble(), randomDouble(), randomDouble()) + val radius = randomDouble(from = 0.0) + val sphereArea = SphereArea(loc, radius) + val json = Json.encodeToString(SphereArea.serializer(), sphereArea) + json shouldEqualJson """ + { + "location": { + "x": ${loc.x}, + "y": ${loc.y}, + "z": ${loc.z}, + "yaw": 0.0, + "pitch": 0.0, + "world": null + }, + "radius": $radius + } + """.trimIndent() + } + + @Test + fun `should with direction coordinate for location`() { + val loc = Location(null, randomDouble(), randomDouble(), randomDouble(), randomFloat(), randomFloat()) + val radius = randomDouble(from = 0.0) + val sphereArea = SphereArea(loc, radius) + val json = Json.encodeToString(SphereArea.serializer(), sphereArea) + json shouldEqualJson """ + { + "location": { + "x": ${loc.x}, + "y": ${loc.y}, + "z": ${loc.z}, + "yaw": ${loc.yaw}, + "pitch": ${loc.pitch}, + "world": null + }, + "radius": $radius + } + """.trimIndent() + } + + @Test + fun `should with all fields`() { + val loc = Location(world, randomDouble(), randomDouble(), randomDouble(), randomFloat(), randomFloat()) + val radius = randomDouble(from = 0.0) + val sphereArea = SphereArea(loc, radius) + val json = Json.encodeToString(SphereArea.serializer(), sphereArea) + json shouldEqualJson """ + { + "location": { + "x": ${loc.x}, + "y": ${loc.y}, + "z": ${loc.z}, + "yaw": ${loc.yaw}, + "pitch": ${loc.pitch}, + "world": "${world.name}" + }, + "radius": $radius + } + """.trimIndent() + } + } + + @Nested + inner class Deserialize { + + @Test + fun `should with all fields`() { + val loc = Location(world, randomDouble(), randomDouble(), randomDouble(), randomFloat(), randomFloat()) + val radius = randomDouble(from = 0.0) + val json = """ + { + "location": { + "x": ${loc.x}, + "y": ${loc.y}, + "z": ${loc.z}, + "yaw": ${loc.yaw}, + "pitch": ${loc.pitch}, + "world": "${world.name}" + }, + "radius": $radius + } + """.trimIndent() + + Json.decodeFromString(SphereArea.serializer(), json) shouldBe SphereArea(loc, radius) + } + + @Test + fun `should throw if radius is negative`() { + val json = """ + { + "location": { + "x": 0.0, + "y": 0.0, + "z": 0.0, + "yaw": 0.0, + "pitch": 0.0, + "world": null + }, + "radius": -1 + } + """.trimIndent() + + shouldThrow { + Json.decodeFromString(SphereArea.serializer(), json) + } + } + } +} diff --git a/src/test/kotlin/com/github/rushyverse/api/world/SphereAreaTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaTest.kt similarity index 95% rename from src/test/kotlin/com/github/rushyverse/api/world/SphereAreaTest.kt rename to src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaTest.kt index 466175fa..1f9a447e 100644 --- a/src/test/kotlin/com/github/rushyverse/api/world/SphereAreaTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaTest.kt @@ -1,25 +1,22 @@ -package com.github.rushyverse.api.world +package com.github.rushyverse.api.world.sphere import be.seeseemelk.mockbukkit.MockBukkit import be.seeseemelk.mockbukkit.ServerMock import be.seeseemelk.mockbukkit.WorldMock import com.github.rushyverse.api.extension.copy -import com.github.rushyverse.api.utils.randomString +import com.github.rushyverse.api.world.SphereArea +import com.github.rushyverse.api.world.isIn import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe -import io.mockk.every import io.mockk.mockk import org.bukkit.Location -import org.bukkit.entity.Player import org.junit.jupiter.api.Nested import org.junit.jupiter.api.assertThrows import kotlin.math.floor -import kotlin.math.round import kotlin.math.sqrt import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test -import kotlin.test.assertContentEquals class SphereAreaTest { From 1d083f085adb44d48d7dc3ec5d50f9db899c61bb Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 30 Jul 2023 12:29:57 +0200 Subject: [PATCH 100/143] tests: Add parameters for random generation --- .../kotlin/com/github/rushyverse/api/utils/Generator.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt b/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt index 0d378edf..bb4c8b8a 100644 --- a/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt +++ b/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt @@ -11,13 +11,13 @@ fun randomString() = stringGenerator.next() fun randomBoolean() = Random.nextBoolean() -fun randomInt() = Random.nextInt() +fun randomInt(from: Int = Int.MIN_VALUE, until: Int = Int.MAX_VALUE) = Random.nextInt(from, until) -fun randomLong() = Random.nextLong() +fun randomLong(from: Long = Long.MIN_VALUE, until: Long = Long.MAX_VALUE) = Random.nextLong(from, until) -fun randomFloat() = randomDouble().toFloat() +fun randomFloat(from: Float = Float.MIN_VALUE, until: Float = Float.MAX_VALUE) = randomDouble(from.toDouble(), until.toDouble()).toFloat() -fun randomDouble() = Random.nextDouble() +fun randomDouble(from: Double = Double.MIN_VALUE, until: Double = Double.MAX_VALUE) = Random.nextDouble(from, until) const val LIMIT_RANDOM_COORDINATE = 1000.0 From 54dea70aa692b4d15660bfade1525464f964d33e Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 30 Jul 2023 17:00:18 +0200 Subject: [PATCH 101/143] tests(area): Add cylinder serializer tests --- .../cylinder/CylinderAreaSerializerTest.kt | 171 ++++++++++++++++++ .../world/{ => cylinder}/CylinderAreaTest.kt | 8 +- 2 files changed, 174 insertions(+), 5 deletions(-) create mode 100644 src/test/kotlin/com/github/rushyverse/api/world/cylinder/CylinderAreaSerializerTest.kt rename src/test/kotlin/com/github/rushyverse/api/world/{ => cylinder}/CylinderAreaTest.kt (96%) diff --git a/src/test/kotlin/com/github/rushyverse/api/world/cylinder/CylinderAreaSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/cylinder/CylinderAreaSerializerTest.kt new file mode 100644 index 00000000..f54bf4cf --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/world/cylinder/CylinderAreaSerializerTest.kt @@ -0,0 +1,171 @@ +package com.github.rushyverse.api.world.cylinder + +import be.seeseemelk.mockbukkit.MockBukkit +import be.seeseemelk.mockbukkit.WorldMock +import com.github.rushyverse.api.utils.randomDouble +import com.github.rushyverse.api.utils.randomFloat +import com.github.rushyverse.api.world.CylinderArea +import com.github.rushyverse.api.world.SphereArea +import io.kotest.assertions.json.shouldEqualJson +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import kotlinx.serialization.json.Json +import org.bukkit.Location +import org.junit.jupiter.api.Nested +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test + +class CylinderAreaSerializerTest { + + private lateinit var world: WorldMock + + @BeforeTest + fun onBefore() { + world = WorldMock() + MockBukkit.mock().apply { + addWorld(world) + } + } + + @AfterTest + fun onAfter() { + MockBukkit.unmock() + } + + @Nested + inner class Serialize { + + @Test + fun `should with only coordinate for location`() { + val loc = Location(null, randomDouble(), randomDouble(), randomDouble()) + val radius = randomDouble(from = 0.0) + val height = randomDouble()..randomDouble() + val area = CylinderArea(loc, radius, height) + val json = Json.encodeToString(CylinderArea.serializer(), area) + json shouldEqualJson """ + { + "location": { + "x": ${loc.x}, + "y": ${loc.y}, + "z": ${loc.z}, + "yaw": 0.0, + "pitch": 0.0, + "world": null + }, + "radius": $radius, + "height": { + "start": ${height.start}, + "end": ${height.endInclusive} + } + } + """.trimIndent() + } + + @Test + fun `should with direction coordinate for location`() { + val loc = Location(null, randomDouble(), randomDouble(), randomDouble(), randomFloat(), randomFloat()) + val radius = randomDouble(from = 0.0) + val height = randomDouble()..randomDouble() + val area = CylinderArea(loc, radius, height) + val json = Json.encodeToString(CylinderArea.serializer(), area) + json shouldEqualJson """ + { + "location": { + "x": ${loc.x}, + "y": ${loc.y}, + "z": ${loc.z}, + "yaw": ${loc.yaw}, + "pitch": ${loc.pitch}, + "world": null + }, + "radius": $radius, + "height": { + "start": ${height.start}, + "end": ${height.endInclusive} + } + } + """.trimIndent() + } + + @Test + fun `should with all fields`() { + val loc = Location(world, randomDouble(), randomDouble(), randomDouble(), randomFloat(), randomFloat()) + val radius = randomDouble(from = 0.0) + val height = randomDouble()..randomDouble() + val area = CylinderArea(loc, radius, height) + val json = Json.encodeToString(CylinderArea.serializer(), area) + json shouldEqualJson """ + { + "location": { + "x": ${loc.x}, + "y": ${loc.y}, + "z": ${loc.z}, + "yaw": ${loc.yaw}, + "pitch": ${loc.pitch}, + "world": "${world.name}" + }, + "radius": $radius, + "height": { + "start": ${height.start}, + "end": ${height.endInclusive} + } + } + """.trimIndent() + } + } + + @Nested + inner class Deserialize { + + @Test + fun `should with all fields`() { + val loc = Location(world, randomDouble(), randomDouble(), randomDouble(), randomFloat(), randomFloat()) + val radius = randomDouble(from = 0.0) + val height = randomDouble()..randomDouble() + val json = """ + { + "location": { + "x": ${loc.x}, + "y": ${loc.y}, + "z": ${loc.z}, + "yaw": ${loc.yaw}, + "pitch": ${loc.pitch}, + "world": "${world.name}" + }, + "radius": $radius, + "height": { + "start": ${height.start}, + "end": ${height.endInclusive} + } + } + """.trimIndent() + Json.decodeFromString(CylinderArea.serializer(), json) shouldBe CylinderArea(loc, radius, height) + } + + @Test + fun `should throw if radius is negative`() { + val json = """ + { + "location": { + "x": 0.0, + "y": 0.0, + "z": 0.0, + "yaw": 0.0, + "pitch": 0.0, + "world": null + }, + "radius": -1, + "height": { + "start": 0.0, + "end": 0.0 + } + } + """.trimIndent() + + shouldThrow { + Json.decodeFromString(CylinderArea.serializer(), json) + } + } + } +} diff --git a/src/test/kotlin/com/github/rushyverse/api/world/CylinderAreaTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/cylinder/CylinderAreaTest.kt similarity index 96% rename from src/test/kotlin/com/github/rushyverse/api/world/CylinderAreaTest.kt rename to src/test/kotlin/com/github/rushyverse/api/world/cylinder/CylinderAreaTest.kt index 45876634..22534876 100644 --- a/src/test/kotlin/com/github/rushyverse/api/world/CylinderAreaTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/world/cylinder/CylinderAreaTest.kt @@ -1,22 +1,20 @@ -package com.github.rushyverse.api.world +package com.github.rushyverse.api.world.cylinder import be.seeseemelk.mockbukkit.MockBukkit import be.seeseemelk.mockbukkit.ServerMock import be.seeseemelk.mockbukkit.WorldMock import com.github.rushyverse.api.extension.copy -import com.github.rushyverse.api.utils.randomString +import com.github.rushyverse.api.world.CylinderArea +import com.github.rushyverse.api.world.isIn import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe -import io.mockk.every import io.mockk.mockk import org.bukkit.Location -import org.bukkit.entity.Player import org.junit.jupiter.api.Nested import org.junit.jupiter.api.assertThrows import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test -import kotlin.test.assertContentEquals class CylinderAreaTest { From 7e308f8f0a1d24c8b88dfebfb5e1e71a4c9e11b6 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 30 Jul 2023 17:00:30 +0200 Subject: [PATCH 102/143] tests(area): Change variable name --- .../api/world/sphere/SphereAreaSerializerTest.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaSerializerTest.kt index 1bc31596..031201ec 100644 --- a/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaSerializerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaSerializerTest.kt @@ -39,8 +39,8 @@ class SphereAreaSerializerTest { fun `should with only coordinate for location`() { val loc = Location(null, randomDouble(), randomDouble(), randomDouble()) val radius = randomDouble(from = 0.0) - val sphereArea = SphereArea(loc, radius) - val json = Json.encodeToString(SphereArea.serializer(), sphereArea) + val area = SphereArea(loc, radius) + val json = Json.encodeToString(SphereArea.serializer(), area) json shouldEqualJson """ { "location": { @@ -60,8 +60,8 @@ class SphereAreaSerializerTest { fun `should with direction coordinate for location`() { val loc = Location(null, randomDouble(), randomDouble(), randomDouble(), randomFloat(), randomFloat()) val radius = randomDouble(from = 0.0) - val sphereArea = SphereArea(loc, radius) - val json = Json.encodeToString(SphereArea.serializer(), sphereArea) + val area = SphereArea(loc, radius) + val json = Json.encodeToString(SphereArea.serializer(), area) json shouldEqualJson """ { "location": { @@ -81,8 +81,8 @@ class SphereAreaSerializerTest { fun `should with all fields`() { val loc = Location(world, randomDouble(), randomDouble(), randomDouble(), randomFloat(), randomFloat()) val radius = randomDouble(from = 0.0) - val sphereArea = SphereArea(loc, radius) - val json = Json.encodeToString(SphereArea.serializer(), sphereArea) + val area = SphereArea(loc, radius) + val json = Json.encodeToString(SphereArea.serializer(), area) json shouldEqualJson """ { "location": { From bf39a5fc586be881586fcbde705034e6c537cd49 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 30 Jul 2023 18:11:56 +0200 Subject: [PATCH 103/143] tests(area): Add cube tests --- .../rushyverse/api/world/cube/CubeAreaTest.kt | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaTest.kt diff --git a/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaTest.kt new file mode 100644 index 00000000..57541aa0 --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaTest.kt @@ -0,0 +1,164 @@ +package com.github.rushyverse.api.world.cube + +import be.seeseemelk.mockbukkit.MockBukkit +import be.seeseemelk.mockbukkit.ServerMock +import be.seeseemelk.mockbukkit.WorldMock +import com.github.rushyverse.api.extension.copy +import com.github.rushyverse.api.utils.assertEqualsLocation +import com.github.rushyverse.api.utils.randomFloat +import com.github.rushyverse.api.utils.randomLocation +import com.github.rushyverse.api.utils.randomString +import com.github.rushyverse.api.world.CubeArea +import com.github.rushyverse.api.world.CylinderArea +import com.github.rushyverse.api.world.isIn +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.mockk.mockk +import org.bukkit.Location +import org.checkerframework.checker.units.qual.min +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import kotlin.math.floor +import kotlin.math.sqrt +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test + +class CubeAreaTest { + + private lateinit var serverMock: ServerMock + private lateinit var worldMock: WorldMock + + @BeforeTest + fun onBefore() { + serverMock = MockBukkit.mock() + worldMock = serverMock.addSimpleWorld("world") + } + + @AfterTest + fun onAfter() { + MockBukkit.unmock() + } + + @Nested + inner class Instantiation { + + @Test + fun `should throw if locations are not in the same world`() { + assertThrows { + CubeArea(randomLocation(mockk()), randomLocation(mockk())) + } + } + + @Test + fun `should set min and max ignoring direction`() { + val loc1 = Location(worldMock, 0.0, 0.0, 0.0, randomFloat(), randomFloat()) + val loc2 = Location(worldMock, 1.0, 1.0, 1.0, randomFloat(), randomFloat()) + CubeArea(loc1, loc2).apply { + assertEqualsLocation(loc1.copy(yaw = 0f, pitch = 0f), min) + assertEqualsLocation(loc2.copy(yaw = 0f, pitch = 0f), max) + } + } + + @Test + fun `should set min and max with same order if coordinate is correctly ordered`() { + val loc1 = Location(worldMock, 0.0, 0.0, 0.0) + val loc2 = Location(worldMock, 1.0, 1.0, 1.0) + CubeArea(loc1, loc2).apply { + assertEqualsLocation(loc1, min) + assertEqualsLocation(loc2, max) + } + } + + @Test + fun `should set min and max with change x order`() { + val loc1 = Location(worldMock, 4.0, 0.0, 0.0) + val loc2 = Location(worldMock, -5.0, 1.0, 1.0) + CubeArea(loc1, loc2).apply { + assertEqualsLocation(loc1.copy(x = loc2.x), min) + assertEqualsLocation(loc2.copy(x = loc1.x), max) + } + } + + @Test + fun `should set min and max with change y order`() { + val loc1 = Location(worldMock, 0.0, 3.0, 0.0) + val loc2 = Location(worldMock, 1.0, -10.0, 1.0) + CubeArea(loc1, loc2).apply { + assertEqualsLocation(loc1.copy(y = loc2.y), min) + assertEqualsLocation(loc2.copy(y = loc1.y), max) + } + } + + @Test + fun `should set min and max with change z order`() { + val loc1 = Location(worldMock, 0.0, 3.0, 17.0) + val loc2 = Location(worldMock, 1.0, 6.0, 2.0) + CubeArea(loc1, loc2).apply { + assertEqualsLocation(loc1.copy(z = loc2.z), min) + assertEqualsLocation(loc2.copy(z = loc1.z), max) + } + } + + @Test + fun `should set min and max with change all coordinates order`() { + val loc1 = Location(worldMock, -1.0, 17.0, 30.0) + val loc2 = Location(worldMock, -9.0, 10.7, 0.0) + CubeArea(loc1, loc2).apply { + assertEqualsLocation(loc2, min) + assertEqualsLocation(loc1, max) + } + } + + } + + @Nested + inner class InAreaWithWorld { + + @Test + fun `should return false if world is different`() { + val min = Location(worldMock, 0.0, 0.0, 0.0) + val max = Location(worldMock, 1.0, 1.0, 1.0) + val area = CubeArea(min, max) + + min.copy(world = serverMock.addSimpleWorld(randomString())) isIn area shouldBe false + } + + @Test + fun `should return true if world is same and in area`() { + val min = Location(worldMock, 0.0, 0.0, 0.0) + val max = Location(worldMock, 1.0, 1.0, 1.0) + val area = CubeArea(min, max) + + min isIn area shouldBe true + } + } + + @Nested + inner class InArea { + + @Test + fun `should detect if location is in area`() { + val min = Location(worldMock, 0.0, 0.0, 0.0) + val max = Location(worldMock, 10.0, 10.0, 10.0) + val area = CubeArea(min, max) + + for (x in 0..10) { + for (y in 0..10) { + for (z in 0..10) { + val loc = Location(worldMock, x.toDouble(), y.toDouble(), z.toDouble()) + loc isIn area shouldBe true + } + } + } + + min.copy(x = 10.1) isIn area shouldBe false + min.copy(y = 10.1) isIn area shouldBe false + min.copy(z = 10.1) isIn area shouldBe false + max.copy(x = -0.1) isIn area shouldBe false + max.copy(y = -0.1) isIn area shouldBe false + max.copy(z = -0.1) isIn area shouldBe false + } + + } +} From de64b0841836afebb96d1e86a3a92f561c40a5bc Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 30 Jul 2023 18:13:08 +0200 Subject: [PATCH 104/143] tests: format --- .../github/rushyverse/api/world/SphereArea.kt | 1 - .../rushyverse/api/world/cube/CubeAreaTest.kt | 5 --- .../cylinder/CylinderAreaSerializerTest.kt | 1 - .../api/world/cylinder/CylinderAreaTest.kt | 36 +++++++++++++++---- .../api/world/sphere/SphereAreaTest.kt | 28 +++++++++++++-- 5 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/world/SphereArea.kt b/src/main/kotlin/com/github/rushyverse/api/world/SphereArea.kt index 0f5a9c2c..aec05a10 100644 --- a/src/main/kotlin/com/github/rushyverse/api/world/SphereArea.kt +++ b/src/main/kotlin/com/github/rushyverse/api/world/SphereArea.kt @@ -97,7 +97,6 @@ public class SphereArea( } override fun isInArea(location: Location): Boolean { - println("Checking if $location is in ${this.location} : ${location.distanceSquared(this.location)}") val areaLocation = this.location return location.world === areaLocation.world // Same world && location.distanceSquared(areaLocation) <= radius.pow(2) // Distance is less than or equal to radius diff --git a/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaTest.kt index 57541aa0..a4815bd4 100644 --- a/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaTest.kt @@ -9,17 +9,12 @@ import com.github.rushyverse.api.utils.randomFloat import com.github.rushyverse.api.utils.randomLocation import com.github.rushyverse.api.utils.randomString import com.github.rushyverse.api.world.CubeArea -import com.github.rushyverse.api.world.CylinderArea import com.github.rushyverse.api.world.isIn -import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe import io.mockk.mockk import org.bukkit.Location -import org.checkerframework.checker.units.qual.min import org.junit.jupiter.api.Nested import org.junit.jupiter.api.assertThrows -import kotlin.math.floor -import kotlin.math.sqrt import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test diff --git a/src/test/kotlin/com/github/rushyverse/api/world/cylinder/CylinderAreaSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/cylinder/CylinderAreaSerializerTest.kt index f54bf4cf..682773b7 100644 --- a/src/test/kotlin/com/github/rushyverse/api/world/cylinder/CylinderAreaSerializerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/world/cylinder/CylinderAreaSerializerTest.kt @@ -5,7 +5,6 @@ import be.seeseemelk.mockbukkit.WorldMock import com.github.rushyverse.api.utils.randomDouble import com.github.rushyverse.api.utils.randomFloat import com.github.rushyverse.api.world.CylinderArea -import com.github.rushyverse.api.world.SphereArea import io.kotest.assertions.json.shouldEqualJson import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe diff --git a/src/test/kotlin/com/github/rushyverse/api/world/cylinder/CylinderAreaTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/cylinder/CylinderAreaTest.kt index 22534876..a77b13a3 100644 --- a/src/test/kotlin/com/github/rushyverse/api/world/cylinder/CylinderAreaTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/world/cylinder/CylinderAreaTest.kt @@ -4,6 +4,7 @@ import be.seeseemelk.mockbukkit.MockBukkit import be.seeseemelk.mockbukkit.ServerMock import be.seeseemelk.mockbukkit.WorldMock import com.github.rushyverse.api.extension.copy +import com.github.rushyverse.api.utils.randomString import com.github.rushyverse.api.world.CylinderArea import com.github.rushyverse.api.world.isIn import io.kotest.assertions.throwables.shouldThrow @@ -63,10 +64,30 @@ class CylinderAreaTest { } @Nested - inner class UpdateWithYChange { + inner class InAreaWithWorld { @Test - fun `should use negative y limit`() { + fun `should return false if world is different`() { + val min = Location(worldMock, 0.0, 0.0, 0.0) + val area = CylinderArea(min, 1.0, 0.0..0.0) + + min.copy(world = serverMock.addSimpleWorld(randomString())) isIn area shouldBe false + } + + @Test + fun `should return true if world is same and in area`() { + val min = Location(worldMock, 0.0, 0.0, 0.0) + val area = CylinderArea(min, 1.0, 0.0..0.0) + + min isIn area shouldBe true + } + } + + @Nested + inner class InAreaWithHeight { + + @Test + fun `should use negative height limit`() { val min = Location(worldMock, 0.0, 0.0, 0.0) val area = CylinderArea(min, 1.0, -10.0..-5.0) @@ -77,7 +98,7 @@ class CylinderAreaTest { } @Test - fun `should use positive y limit`() { + fun `should use positive height limit`() { val min = Location(worldMock, 0.0, 0.0, 0.0) val area = CylinderArea(min, 0.0, 5.0..10.0) @@ -88,7 +109,7 @@ class CylinderAreaTest { } @Test - fun `should use negative and positive y limit`() { + fun `should use negative and positive height limit`() { val min = Location(worldMock, 0.0, 0.0, 0.0) val area = CylinderArea(min, 0.0, -5.0..10.0) @@ -100,7 +121,7 @@ class CylinderAreaTest { } @Test - fun `should use zero y limit`() { + fun `should use zero height limit`() { val min = Location(worldMock, 0.0, 0.0, 0.0) val area = CylinderArea(min, 0.0, 0.0..0.0) @@ -112,7 +133,7 @@ class CylinderAreaTest { } @Nested - inner class UpdateWithRadiusChange { + inner class InAreaWithRadius { @Test fun `should use zero for radius`() { @@ -150,6 +171,9 @@ class CylinderAreaTest { min.copy(x = -0.1, z = 1.0) isIn area shouldBe false min.copy(x = 0.1, z = -1.0) isIn area shouldBe false min.copy(x = -0.1, z = -1.0) isIn area shouldBe false + + min.copy(y = 0.1) isIn area shouldBe false + min.copy(y = -0.1) isIn area shouldBe false } } } diff --git a/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaTest.kt index 1f9a447e..50e8e9e5 100644 --- a/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaTest.kt @@ -4,6 +4,7 @@ import be.seeseemelk.mockbukkit.MockBukkit import be.seeseemelk.mockbukkit.ServerMock import be.seeseemelk.mockbukkit.WorldMock import com.github.rushyverse.api.extension.copy +import com.github.rushyverse.api.utils.randomString import com.github.rushyverse.api.world.SphereArea import com.github.rushyverse.api.world.isIn import io.kotest.assertions.throwables.shouldThrow @@ -26,7 +27,7 @@ class SphereAreaTest { @BeforeTest fun onBefore() { serverMock = MockBukkit.mock() - worldMock = serverMock.addSimpleWorld("world") + worldMock = serverMock.addSimpleWorld(randomString()) } @AfterTest @@ -65,7 +66,27 @@ class SphereAreaTest { } @Nested - inner class UpdateWithRadiusChange { + inner class InAreaWithWorld { + + @Test + fun `should return false if world is different`() { + val min = Location(worldMock, 0.0, 0.0, 0.0) + val area = SphereArea(min, 1.0) + + min.copy(world = serverMock.addSimpleWorld(randomString())) isIn area shouldBe false + } + + @Test + fun `should return true if world is same and in area`() { + val min = Location(worldMock, 0.0, 0.0, 0.0) + val area = SphereArea(min, 1.0) + + min isIn area shouldBe true + } + } + + @Nested + inner class InAreaWithRadius { @Test fun `should use zero for radius`() { @@ -108,7 +129,8 @@ class SphereAreaTest { min.copy(x = 5.0, y = 5.0, z = 5.0) isIn area shouldBe false min.copy(x = -5.0, y = -5.0, z = -5.0) isIn area shouldBe false - val maxDiagonal = floor(sqrt(5.0 * 5.0 / 3.0) * 100) / 100 // The limit is 25 for radius 5.0 with x,y,z = ~2.886 + val maxDiagonal = + floor(sqrt(5.0 * 5.0 / 3.0) * 100) / 100 // The limit is 25 for radius 5.0 with x,y,z = ~2.886 min.copy(x = maxDiagonal, y = maxDiagonal, z = maxDiagonal) isIn area shouldBe true min.copy(x = -maxDiagonal, y = -maxDiagonal, z = -maxDiagonal) isIn area shouldBe true min.copy(x = maxDiagonal + 0.1, y = maxDiagonal + 0.1, z = maxDiagonal + 0.1) isIn area shouldBe false From ab5e80dd9daf3ca568ef14e037af9ab74692a3cd Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 30 Jul 2023 18:53:10 +0200 Subject: [PATCH 105/143] tests(area): Add tests for cube area --- .../api/world/cube/CubeAreaSerializerTest.kt | 176 ++++++++++++++++++ .../rushyverse/api/world/cube/CubeAreaTest.kt | 19 ++ 2 files changed, 195 insertions(+) create mode 100644 src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaSerializerTest.kt diff --git a/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaSerializerTest.kt new file mode 100644 index 00000000..e58531f9 --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaSerializerTest.kt @@ -0,0 +1,176 @@ +package com.github.rushyverse.api.world.cube + +import be.seeseemelk.mockbukkit.MockBukkit +import be.seeseemelk.mockbukkit.WorldMock +import com.github.rushyverse.api.extension.minMaxOf +import com.github.rushyverse.api.utils.randomDouble +import com.github.rushyverse.api.utils.randomFloat +import com.github.rushyverse.api.world.CubeArea +import io.kotest.assertions.json.shouldEqualJson +import io.kotest.matchers.shouldBe +import kotlinx.serialization.json.Json +import org.bukkit.Location +import org.junit.jupiter.api.Nested +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test + +class CubeAreaSerializerTest { + + private lateinit var world: WorldMock + + @BeforeTest + fun onBefore() { + world = WorldMock() + MockBukkit.mock().apply { + addWorld(world) + } + } + + @AfterTest + fun onAfter() { + MockBukkit.unmock() + } + + @Nested + inner class Serialize { + + @Test + fun `should with only coordinate for location`() { + val xs = minMaxOf(randomDouble(), randomDouble()) + val ys = minMaxOf(randomDouble(), randomDouble()) + val zs = minMaxOf(randomDouble(), randomDouble()) + val min = Location(null, xs.first, ys.first, zs.first) + val max = Location(null, xs.second, ys.second, zs.second) + val area = CubeArea(min, max) + + val json = Json.encodeToString(CubeArea.serializer(), area) + json shouldEqualJson """ + { + "location1": { + "x": ${min.x}, + "y": ${min.y}, + "z": ${min.z}, + "yaw": 0.0, + "pitch": 0.0, + "world": null + }, + "location2": { + "x": ${max.x}, + "y": ${max.y}, + "z": ${max.z}, + "yaw": 0.0, + "pitch": 0.0, + "world": null + } + } + """.trimIndent() + } + + @Test + fun `should with direction coordinate for location`() { + val xs = minMaxOf(randomDouble(), randomDouble()) + val ys = minMaxOf(randomDouble(), randomDouble()) + val zs = minMaxOf(randomDouble(), randomDouble()) + val yaws = minMaxOf(randomFloat(), randomFloat()) + val pitchs = minMaxOf(randomFloat(), randomFloat()) + val min = Location(null, xs.first, ys.first, zs.first, yaws.first, pitchs.first) + val max = Location(null, xs.second, ys.second, zs.second, yaws.second, pitchs.second) + val area = CubeArea(min, max) + + val json = Json.encodeToString(CubeArea.serializer(), area) + json shouldEqualJson """ + { + "location1": { + "x": ${min.x}, + "y": ${min.y}, + "z": ${min.z}, + "yaw": 0.0, + "pitch": 0.0, + "world": null + }, + "location2": { + "x": ${max.x}, + "y": ${max.y}, + "z": ${max.z}, + "yaw": 0.0, + "pitch": 0.0, + "world": null + } + } + """.trimIndent() + } + + @Test + fun `should with all fields`() { + val xs = minMaxOf(randomDouble(), randomDouble()) + val ys = minMaxOf(randomDouble(), randomDouble()) + val zs = minMaxOf(randomDouble(), randomDouble()) + val yaws = minMaxOf(randomFloat(), randomFloat()) + val pitchs = minMaxOf(randomFloat(), randomFloat()) + val min = Location(world, xs.first, ys.first, zs.first, yaws.first, pitchs.first) + val max = Location(world, xs.second, ys.second, zs.second, yaws.second, pitchs.second) + val area = CubeArea(min, max) + + val json = Json.encodeToString(CubeArea.serializer(), area) + json shouldEqualJson """ + { + "location1": { + "x": ${min.x}, + "y": ${min.y}, + "z": ${min.z}, + "yaw": 0.0, + "pitch": 0.0, + "world": "${world.name}" + }, + "location2": { + "x": ${max.x}, + "y": ${max.y}, + "z": ${max.z}, + "yaw": 0.0, + "pitch": 0.0, + "world": "${world.name}" + } + } + """.trimIndent() + } + } + + @Nested + inner class Deserialize { + + @Test + fun `should with all fields`() { + val xs = minMaxOf(randomDouble(), randomDouble()) + val ys = minMaxOf(randomDouble(), randomDouble()) + val zs = minMaxOf(randomDouble(), randomDouble()) + val yaws = minMaxOf(randomFloat(), randomFloat()) + val pitchs = minMaxOf(randomFloat(), randomFloat()) + + val json = """ + { + "location1": { + "x": ${xs.first}, + "y": ${ys.second}, + "z": ${zs.second}, + "yaw": ${yaws.first}, + "pitch": ${pitchs.second}, + "world": "${world.name}" + }, + "location2": { + "x": ${xs.second}, + "y": ${ys.first}, + "z": ${zs.first}, + "yaw": ${yaws.second}, + "pitch": ${pitchs.first}, + "world": "${world.name}" + } + } + """.trimIndent() + Json.decodeFromString(CubeArea.serializer(), json) shouldBe CubeArea( + Location(world, xs.first, ys.first, zs.first, 0f, 0f), + Location(world, xs.second, ys.second, zs.second, 0f, 0f) + ) + } + } +} diff --git a/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaTest.kt index a4815bd4..454387fe 100644 --- a/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaTest.kt @@ -45,6 +45,16 @@ class CubeAreaTest { } } + @Test + fun `should set min and max with null world`() { + val loc1 = Location(null, 0.0, 0.0, 0.0) + val loc2 = Location(null, 1.0, 1.0, 1.0) + CubeArea(loc1, loc2).apply { + assertEqualsLocation(loc1, min) + assertEqualsLocation(loc2, max) + } + } + @Test fun `should set min and max ignoring direction`() { val loc1 = Location(worldMock, 0.0, 0.0, 0.0, randomFloat(), randomFloat()) @@ -110,6 +120,15 @@ class CubeAreaTest { @Nested inner class InAreaWithWorld { + @Test + fun `should return true if world is null for both`() { + val min = Location(null, 0.0, 0.0, 0.0) + val max = Location(null, 1.0, 1.0, 1.0) + val area = CubeArea(min, max) + + min isIn area shouldBe true + } + @Test fun `should return false if world is different`() { val min = Location(worldMock, 0.0, 0.0, 0.0) From 6e3f54c37c542a0fb097ba4934a8f454a631aeb3 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 30 Jul 2023 18:53:23 +0200 Subject: [PATCH 106/143] chore: Change method name --- .../rushyverse/api/extension/_Comparable.kt | 2 +- .../com/github/rushyverse/api/extension/_Math.kt | 15 --------------- .../rushyverse/api/extension/ComparableExtTest.kt | 14 +++++++------- 3 files changed, 8 insertions(+), 23 deletions(-) delete mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/_Math.kt diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Comparable.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Comparable.kt index 6b79d1be..4f0cdef5 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_Comparable.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Comparable.kt @@ -8,7 +8,7 @@ package com.github.rushyverse.api.extension * @param b Second value. * @return Both values with a defined order. */ -public fun > minMax(a: T, b: T): Pair = if (a <= b) { +public fun > minMaxOf(a: T, b: T): Pair = if (a <= b) { a to b } else { b to a diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Math.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Math.kt deleted file mode 100644 index 4f0cdef5..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_Math.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.github.rushyverse.api.extension - -/** - * Get the lowest and highest value in a specific index. - * The lowest value is placed at index 0 and highest at index 1. - * - * @param a First value. - * @param b Second value. - * @return Both values with a defined order. - */ -public fun > minMaxOf(a: T, b: T): Pair = if (a <= b) { - a to b -} else { - b to a -} diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/ComparableExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/ComparableExtTest.kt index 7dff3184..eec547eb 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/ComparableExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/ComparableExtTest.kt @@ -15,11 +15,11 @@ class ComparableExtTest { fun `a is inferior to b`() { val expectedA = 1 val expectedB = 2 - val (a, b) = minMax(expectedA, expectedB) + val (a, b) = minMaxOf(expectedA, expectedB) assertEquals(expectedA, a) assertEquals(expectedB, b) - val (a2, b2) = minMax(expectedB, expectedA) + val (a2, b2) = minMaxOf(expectedB, expectedA) assertEquals(expectedA, a2) assertEquals(expectedB, b2) } @@ -28,11 +28,11 @@ class ComparableExtTest { fun `a is equals to b`() { val expectedA = 2 val expectedB = 2 - val (a, b) = minMax(expectedA, expectedB) + val (a, b) = minMaxOf(expectedA, expectedB) assertEquals(expectedA, a) assertEquals(expectedB, b) - val (a2, b2) = minMax(expectedB, expectedA) + val (a2, b2) = minMaxOf(expectedB, expectedA) assertEquals(expectedA, a2) assertEquals(expectedB, b2) } @@ -41,13 +41,13 @@ class ComparableExtTest { fun `a is superior to b`() { val expectedA = 3 val expectedB = 2 - val (b, a) = minMax(expectedA, expectedB) + val (b, a) = minMaxOf(expectedA, expectedB) assertEquals(expectedA, a) assertEquals(expectedB, b) - val (b2, a2) = minMax(expectedB, expectedA) + val (b2, a2) = minMaxOf(expectedB, expectedA) assertEquals(expectedA, a2) assertEquals(expectedB, b2) } } -} \ No newline at end of file +} From 18a1750e08f7b86e5f3e305abea32884ca2dab6c Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 30 Jul 2023 18:53:33 +0200 Subject: [PATCH 107/143] tests: format --- .../kotlin/com/github/rushyverse/api/koin/CraftContextTest.kt | 4 ++-- .../com/github/rushyverse/api/player/ClientManagerImplTest.kt | 2 +- src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/test/kotlin/com/github/rushyverse/api/koin/CraftContextTest.kt b/src/test/kotlin/com/github/rushyverse/api/koin/CraftContextTest.kt index 34dfa3a1..8efb797f 100644 --- a/src/test/kotlin/com/github/rushyverse/api/koin/CraftContextTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/koin/CraftContextTest.kt @@ -14,8 +14,8 @@ import kotlin.test.* class CraftContextTest { - @BeforeTest - fun onBefore() { + @AfterTest + fun onAfter() { CraftContext.koins.toMap().forEach { (id, _) -> CraftContext.stopKoin(id) } diff --git a/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt b/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt index 0d639394..6b136001 100644 --- a/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/player/ClientManagerImplTest.kt @@ -16,7 +16,7 @@ import org.junit.jupiter.api.assertThrows import java.util.* import kotlin.test.* -class ClientManagerImplTest: AbstractKoinTest() { +class ClientManagerImplTest : AbstractKoinTest() { private lateinit var clientManager: ClientManager diff --git a/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt b/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt index bb4c8b8a..76009c52 100644 --- a/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt +++ b/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt @@ -15,7 +15,8 @@ fun randomInt(from: Int = Int.MIN_VALUE, until: Int = Int.MAX_VALUE) = Random.ne fun randomLong(from: Long = Long.MIN_VALUE, until: Long = Long.MAX_VALUE) = Random.nextLong(from, until) -fun randomFloat(from: Float = Float.MIN_VALUE, until: Float = Float.MAX_VALUE) = randomDouble(from.toDouble(), until.toDouble()).toFloat() +fun randomFloat(from: Float = Float.MIN_VALUE, until: Float = Float.MAX_VALUE) = + randomDouble(from.toDouble(), until.toDouble()).toFloat() fun randomDouble(from: Double = Double.MIN_VALUE, until: Double = Double.MAX_VALUE) = Random.nextDouble(from, until) From 4bb903bb26f3c732f9869264d3b167d005ca1a22 Mon Sep 17 00:00:00 2001 From: Quentin Date: Wed, 2 Aug 2023 15:52:24 +0200 Subject: [PATCH 108/143] chore: Use adventure.FastBoard to use components --- src/main/kotlin/com/github/rushyverse/api/player/Client.kt | 2 +- .../rushyverse/api/player/scoreboard/ScoreboardManager.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/player/Client.kt b/src/main/kotlin/com/github/rushyverse/api/player/Client.kt index 9887a1c4..16f62535 100644 --- a/src/main/kotlin/com/github/rushyverse/api/player/Client.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/Client.kt @@ -5,7 +5,7 @@ import com.github.rushyverse.api.koin.inject import com.github.rushyverse.api.player.exception.PlayerNotFoundException import com.github.rushyverse.api.player.scoreboard.ScoreboardManager import com.github.rushyverse.api.translation.SupportedLanguage -import fr.mrmicky.fastboard.FastBoard +import fr.mrmicky.fastboard.adventure.FastBoard import kotlinx.coroutines.CoroutineScope import net.kyori.adventure.text.Component import net.kyori.adventure.text.Component.text diff --git a/src/main/kotlin/com/github/rushyverse/api/player/scoreboard/ScoreboardManager.kt b/src/main/kotlin/com/github/rushyverse/api/player/scoreboard/ScoreboardManager.kt index 0932e330..e5d4c043 100644 --- a/src/main/kotlin/com/github/rushyverse/api/player/scoreboard/ScoreboardManager.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/scoreboard/ScoreboardManager.kt @@ -1,6 +1,6 @@ package com.github.rushyverse.api.player.scoreboard -import fr.mrmicky.fastboard.FastBoard +import fr.mrmicky.fastboard.adventure.FastBoard import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.bukkit.entity.Player From 4ab7b587d335118bf11d8b228d1cf6fafa76ad61 Mon Sep 17 00:00:00 2001 From: Cize <77901878+Cizetux@users.noreply.github.com> Date: Wed, 2 Aug 2023 22:12:42 +0200 Subject: [PATCH 109/143] chore: Add german supported language --- .../github/rushyverse/api/translation/SupportedLanguage.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt b/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt index ce5d52e2..73e6c2c6 100644 --- a/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt +++ b/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt @@ -9,5 +9,6 @@ public enum class SupportedLanguage(public val displayName: String, public val l ENGLISH("English", Locale("en", "gb")), FRENCH("Français", Locale("fr", "fr")), - SPANISH("Español", Locale("es", "es")) -} \ No newline at end of file + SPANISH("Español", Locale("es", "es")), + GERMAN("Deutsch", Locale("de", "de")) +} From 3b7bb78f4b1f6b9a4a060e404deda7104e81583b Mon Sep 17 00:00:00 2001 From: Cize <77901878+Cizetux@users.noreply.github.com> Date: Wed, 2 Aug 2023 22:32:24 +0200 Subject: [PATCH 110/143] chore(translation): Add chinese in supported languages (#63) --- .../com/github/rushyverse/api/translation/SupportedLanguage.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt b/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt index 73e6c2c6..33f28871 100644 --- a/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt +++ b/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt @@ -10,5 +10,6 @@ public enum class SupportedLanguage(public val displayName: String, public val l ENGLISH("English", Locale("en", "gb")), FRENCH("Français", Locale("fr", "fr")), SPANISH("Español", Locale("es", "es")), - GERMAN("Deutsch", Locale("de", "de")) + GERMAN("Deutsch", Locale("de", "de")), + CHINESE("Chinese", Locale("zh", "zh")) } From 19312c82b3bc60923c1a6c0611a4c87cd3045202 Mon Sep 17 00:00:00 2001 From: Cizetux Date: Wed, 2 Aug 2023 22:40:39 +0200 Subject: [PATCH 111/143] fix: Chinese country code --- .../github/rushyverse/api/translation/SupportedLanguage.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt b/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt index 33f28871..274d51df 100644 --- a/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt +++ b/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt @@ -11,5 +11,5 @@ public enum class SupportedLanguage(public val displayName: String, public val l FRENCH("Français", Locale("fr", "fr")), SPANISH("Español", Locale("es", "es")), GERMAN("Deutsch", Locale("de", "de")), - CHINESE("Chinese", Locale("zh", "zh")) -} + CHINESE("Chinese", Locale("zh", "ch")) +} \ No newline at end of file From 6077080eeca1aa1a9a348e13f8fc93496d828d3e Mon Sep 17 00:00:00 2001 From: Distractic Date: Wed, 2 Aug 2023 23:22:18 +0200 Subject: [PATCH 112/143] chore(deps): Add ICU for plural translation --- build.gradle.kts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 1a5f7d02..9afb39da 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { val slf4jVersion = "2.0.0-alpha6" val fastboardVersion = "2.0.0" val kotestVersion = "5.6.2" + val icu4jVersion = "73.2" implementation(kotlin("stdlib")) implementation(kotlin("stdlib-jdk8")) @@ -40,6 +41,9 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutineVersion") implementation("io.github.microutils:kotlin-logging:$loggingVersion") + // Plural translation + implementation("com.ibm.icu:icu4j:$icu4jVersion") + // Injection framework implementation("io.insert-koin:koin-core:$koinVersion") implementation("io.insert-koin:koin-logger-slf4j:$koinVersion") From 2230dd7ca3d44089b7c78d0ad5d9f35dce370a3c Mon Sep 17 00:00:00 2001 From: Distractic Date: Wed, 2 Aug 2023 23:22:36 +0200 Subject: [PATCH 113/143] fix: Use ICU for plural translation --- .../api/translation/ResourceBundleTranslationProvider.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProvider.kt b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProvider.kt index b7c9db0b..43ae20cd 100644 --- a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProvider.kt +++ b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProvider.kt @@ -1,7 +1,7 @@ package com.github.rushyverse.api.translation +import com.ibm.icu.text.MessageFormat import mu.KotlinLogging -import java.text.MessageFormat import java.util.* /** From b01469477bc628cdd6c1a592c91a17c9a4f5948d Mon Sep 17 00:00:00 2001 From: Distractic Date: Wed, 2 Aug 2023 23:23:03 +0200 Subject: [PATCH 114/143] tests(translation): Add unit tests for bundle translation --- .../ResourceBundleTranslationProviderTest.kt | 198 ++++++++++++++++++ src/test/resources/test_bundle.properties | 4 +- .../resources/test_bundle_de_DE.properties | 3 + .../resources/test_bundle_es_ES.properties | 3 + .../resources/test_bundle_fr_FR.properties | 3 +- .../resources/test_bundle_zh_CH.properties | 3 + 6 files changed, 210 insertions(+), 4 deletions(-) create mode 100644 src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProviderTest.kt create mode 100644 src/test/resources/test_bundle_de_DE.properties create mode 100644 src/test/resources/test_bundle_es_ES.properties create mode 100644 src/test/resources/test_bundle_zh_CH.properties diff --git a/src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProviderTest.kt b/src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProviderTest.kt new file mode 100644 index 00000000..af29d95b --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProviderTest.kt @@ -0,0 +1,198 @@ +package com.github.rushyverse.api.translation + +import com.github.rushyverse.api.utils.randomString +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import java.util.* +import kotlin.test.BeforeTest +import kotlin.test.Test + +private const val BUNDLE_NAME = "test_bundle" + +private const val SECOND_BUNDLE_NAME = "test_bundle_2" + +class ResourceBundleTranslationProviderTest { + + private lateinit var provider: ResourceBundleTranslationProvider + + @BeforeTest + fun onBefore() { + provider = ResourceBundleTranslationProvider() + } + + @Nested + inner class RegisterResourceBundle { + + @Test + fun `should load a resource bundle`() { + val locale = SupportedLanguage.ENGLISH.locale + provider.registerResourceBundle(BUNDLE_NAME, locale, ResourceBundle::getBundle) + provider.get("test1", locale, BUNDLE_NAME) shouldBe "english_value_1" + provider.get("test2", locale, BUNDLE_NAME) shouldBe "english_value_2" + } + + @Test + fun `should load a resource bundle for all supported locales`() { + provider.registerResourceBundleForSupportedLocales(BUNDLE_NAME, ResourceBundle::getBundle) + SupportedLanguage.entries.forEach { + val displayName = it.displayName.lowercase() + provider.get("test1", it.locale, BUNDLE_NAME) shouldBe "${displayName}_value_1" + provider.get("test2", it.locale, BUNDLE_NAME) shouldBe "${displayName}_value_2" + } + } + + @Test + fun `should load multiple resource bundles`() { + val locale = SupportedLanguage.ENGLISH.locale + provider.registerResourceBundle(BUNDLE_NAME, locale, ResourceBundle::getBundle) + provider.registerResourceBundle(SECOND_BUNDLE_NAME, locale, ResourceBundle::getBundle) + provider.get("test1", locale, BUNDLE_NAME) shouldBe "english_value_1" + provider.get("simple_value", locale, SECOND_BUNDLE_NAME) shouldBe "English value" + } + } + + @Nested + inner class GetValue { + + @Test + fun `should throw an exception if the bundle is not registered`() { + val locale = SupportedLanguage.ENGLISH.locale + val ex = assertThrows { + provider.get("test1", locale, BUNDLE_NAME) + } + ex.bundleName shouldBe BUNDLE_NAME + ex.locale shouldBe locale + } + + @Test + fun `should throw an exception if the key is not found`() { + provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) + assertThrows { + provider.get(randomString(), SupportedLanguage.ENGLISH.locale, BUNDLE_NAME) + } + } + + @Test + fun `should return the value for the given key`() { + provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) + provider.get("test1", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME) shouldBe "english_value_1" + } + + @Test + fun `should return the default value if the value is not defined for language`() { + provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) + provider.get("test_undefined", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME) shouldBe "default_value" + } + } + + @Nested + inner class TranslateValue { + + @Test + fun `should return the value for the given key`() { + provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) + provider.translate("test1", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME) shouldBe "english_value_1" + } + + @Test + fun `should return the value for the given key with the given array arguments`() { + provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) + provider.translate( + "test_args", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME, arrayOf( + "with arguments" + ) + ) shouldBe "english_value with arguments" + } + + @Test + fun `should return the value for the given key with the given list arguments`() { + provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) + provider.translate( + "test_args", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME, listOf( + "with arguments" + ) + ) shouldBe "english_value with arguments" + } + + @Test + fun `should return the key if the bundle is not registered`() { + val locale = SupportedLanguage.ENGLISH.locale + val ex = assertThrows { + provider.translate("test1", locale, BUNDLE_NAME) + } + ex.bundleName shouldBe BUNDLE_NAME + ex.locale shouldBe locale + } + + @Test + fun `should return the key if the value is not defined for language`() { + provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) + val key = randomString() + provider.translate(key, SupportedLanguage.ENGLISH.locale, BUNDLE_NAME) shouldBe key + } + + @Test + fun `should return the key if the value is not defined for language with the given array arguments`() { + provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) + val key = randomString() + provider.translate(key, SupportedLanguage.ENGLISH.locale, BUNDLE_NAME, arrayOf("test")) shouldBe key + } + + @Test + fun `should return the key if the value is not defined for language with the given list arguments`() { + provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) + val key = randomString() + provider.translate(key, SupportedLanguage.ENGLISH.locale, BUNDLE_NAME, listOf("test")) shouldBe key + } + + @Test + fun `should return the default value if the value is not defined for language`() { + provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) + provider.translate("test_undefined", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME) shouldBe "default_value" + } + + @Test + fun `should return the value with template for args if no replacement args are defined`() { + provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) + provider.translate("test_args", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME) shouldBe "english_value {0}" + } + + @Test + fun `should return the value with plural syntax`() { + provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) + provider.translate( + "test_plural", + SupportedLanguage.ENGLISH.locale, + BUNDLE_NAME, + arrayOf(2) + ) shouldBe "Need 2 players." + } + + @Test + fun `should return the value with singular syntax`() { + provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) + + provider.translate( + "test_plural", + SupportedLanguage.ENGLISH.locale, + BUNDLE_NAME, + arrayOf(1) + ) shouldBe "Need 1 player." + + provider.translate( + "test_plural", + SupportedLanguage.ENGLISH.locale, + BUNDLE_NAME, + arrayOf(0) + ) shouldBe "Need 0 player." + } + + @Test + fun `should return the UTF-8 value`() { + provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.FRENCH.locale, ResourceBundle::getBundle) + provider.translate("test1", SupportedLanguage.FRENCH.locale, BUNDLE_NAME) shouldBe "français_value_1" + } + + } +} diff --git a/src/test/resources/test_bundle.properties b/src/test/resources/test_bundle.properties index 608302c4..78c88bf7 100644 --- a/src/test/resources/test_bundle.properties +++ b/src/test/resources/test_bundle.properties @@ -1,5 +1,5 @@ -test_plural=Need {0} {0, plural, =0 {player} =1 {player} other {players}}. +test_plural= test1=default_value_1 test2=default_value_2 test_args=default_value {0} -test_undefined=default_value \ No newline at end of file +test_undefined=default_value diff --git a/src/test/resources/test_bundle_de_DE.properties b/src/test/resources/test_bundle_de_DE.properties new file mode 100644 index 00000000..60ff99b7 --- /dev/null +++ b/src/test/resources/test_bundle_de_DE.properties @@ -0,0 +1,3 @@ +test1=deutsch_value_1 +test2=deutsch_value_2 +test_args=deutsch_value {0} diff --git a/src/test/resources/test_bundle_es_ES.properties b/src/test/resources/test_bundle_es_ES.properties new file mode 100644 index 00000000..56fa3743 --- /dev/null +++ b/src/test/resources/test_bundle_es_ES.properties @@ -0,0 +1,3 @@ +test1=español_value_1 +test2=español_value_2 +test_args=español_value {0} diff --git a/src/test/resources/test_bundle_fr_FR.properties b/src/test/resources/test_bundle_fr_FR.properties index 6ef50e69..b041caee 100644 --- a/src/test/resources/test_bundle_fr_FR.properties +++ b/src/test/resources/test_bundle_fr_FR.properties @@ -1,4 +1,3 @@ -test_plural=Besoin de {0} {0, plural, =0 {joueur} =1 {joueur} other {joueurs}}. test1=français_value_1 test2=français_value_2 -test_args=français_value {0} \ No newline at end of file +test_args=français_value {0} diff --git a/src/test/resources/test_bundle_zh_CH.properties b/src/test/resources/test_bundle_zh_CH.properties new file mode 100644 index 00000000..46a3d95e --- /dev/null +++ b/src/test/resources/test_bundle_zh_CH.properties @@ -0,0 +1,3 @@ +test1=chinese_value_1 +test2=chinese_value_2 +test_args=chinese_value {0} From 2a3b279b6286156cdaf7a9b8f7d6bf23365ae168 Mon Sep 17 00:00:00 2001 From: Distractic Date: Thu, 3 Aug 2023 13:39:23 +0200 Subject: [PATCH 115/143] chore: Remove list arg for translation --- .../ResourceBundleTranslationProvider.kt | 4 ++-- .../api/translation/TranslationProvider.kt | 21 +------------------ .../ResourceBundleTranslationProviderTest.kt | 17 --------------- 3 files changed, 3 insertions(+), 39 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProvider.kt b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProvider.kt index 43ae20cd..3894ba3e 100644 --- a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProvider.kt +++ b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProvider.kt @@ -35,7 +35,7 @@ public open class ResourceBundleTranslationProvider : TranslationProvider() { key: String, locale: Locale, bundleName: String, - replacements: Array + arguments: Array ): String { val string = try { get(key, locale, bundleName) @@ -44,7 +44,7 @@ public open class ResourceBundleTranslationProvider : TranslationProvider() { return key } - return MessageFormat(string, locale).format(replacements) + return MessageFormat(string, locale).format(arguments) } /** diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/TranslationProvider.kt b/src/main/kotlin/com/github/rushyverse/api/translation/TranslationProvider.kt index 4e6e4c95..00f7af6e 100644 --- a/src/main/kotlin/com/github/rushyverse/api/translation/TranslationProvider.kt +++ b/src/main/kotlin/com/github/rushyverse/api/translation/TranslationProvider.kt @@ -19,25 +19,6 @@ public abstract class TranslationProvider { key: String, locale: Locale, bundleName: String, - replacements: Array + arguments: Array = emptyArray() ): String - - /** - * Get a formatted translation using the provided arguments. - */ - public fun translate( - key: String, - locale: Locale, - bundleName: String - ): String = translate(key, locale, bundleName, emptyArray()) - - /** - * Get a formatted translation using the provided arguments. - */ - public fun translate( - key: String, - locale: Locale, - bundleName: String, - replacements: Collection - ): String = translate(key, locale, bundleName, replacements.toTypedArray()) } diff --git a/src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProviderTest.kt b/src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProviderTest.kt index af29d95b..3f1bb9e5 100644 --- a/src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProviderTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProviderTest.kt @@ -105,16 +105,6 @@ class ResourceBundleTranslationProviderTest { ) shouldBe "english_value with arguments" } - @Test - fun `should return the value for the given key with the given list arguments`() { - provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - provider.translate( - "test_args", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME, listOf( - "with arguments" - ) - ) shouldBe "english_value with arguments" - } - @Test fun `should return the key if the bundle is not registered`() { val locale = SupportedLanguage.ENGLISH.locale @@ -139,13 +129,6 @@ class ResourceBundleTranslationProviderTest { provider.translate(key, SupportedLanguage.ENGLISH.locale, BUNDLE_NAME, arrayOf("test")) shouldBe key } - @Test - fun `should return the key if the value is not defined for language with the given list arguments`() { - provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - val key = randomString() - provider.translate(key, SupportedLanguage.ENGLISH.locale, BUNDLE_NAME, listOf("test")) shouldBe key - } - @Test fun `should return the default value if the value is not defined for language`() { provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) From 0857dd7eace04974221f373d1682583eb12318ed Mon Sep 17 00:00:00 2001 From: Quentin Date: Thu, 3 Aug 2023 15:46:16 +0200 Subject: [PATCH 116/143] chore: Add color val and fix translation of TeamType --- .../rushyverse/api/game/team/TeamType.kt | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt b/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt index 4a2ccf23..f6b63459 100644 --- a/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt +++ b/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt @@ -5,25 +5,28 @@ import com.github.rushyverse.api.translation.SupportedLanguage import com.github.rushyverse.api.translation.TranslationProvider import net.kyori.adventure.text.Component import net.kyori.adventure.text.Component.text +import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.format.TextColor import java.util.* -public enum class TeamType { - - WHITE, - RED, - BLUE, - GREEN, - YELLOW, - PURPLE, - AQUA, - ORANGE, - BLACK +public enum class TeamType( + public val color: TextColor +) { + WHITE(NamedTextColor.WHITE), + RED(NamedTextColor.RED), + BLUE(NamedTextColor.BLUE), + GREEN(NamedTextColor.GREEN), + YELLOW(NamedTextColor.YELLOW), + PURPLE(NamedTextColor.LIGHT_PURPLE), + AQUA(NamedTextColor.AQUA), + ORANGE(NamedTextColor.GOLD), + BLACK(NamedTextColor.BLACK) ; public fun name( translationProvider: TranslationProvider, locale: Locale = SupportedLanguage.ENGLISH.locale - ): String = translationProvider.translate("team.${name}", locale, BUNDLE_API) + ): String = translationProvider.translate("team.${name.lowercase()}", locale, BUNDLE_API) public fun textName( translationProvider: TranslationProvider, From 4536fa666e3048624a8c6a59348ecfe18904a126 Mon Sep 17 00:00:00 2001 From: Distractic Date: Thu, 3 Aug 2023 18:16:40 +0200 Subject: [PATCH 117/143] feat: Add extension function to color string --- .../rushyverse/api/extension/_String.kt | 10 ++++++ .../rushyverse/api/extension/StringExtTest.kt | 32 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt index 861e22c4..b523b5da 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt @@ -25,6 +25,16 @@ public const val UUID_SIZE: Int = 36 */ public fun String.colored(): String = ChatColor.translateAlternateColorCodes('&', this) +/** + * Wraps a given string with a color tag. + * Example: "Hello".wrapColorWith("red") will return `Hello`. + * + * @receiver The string to be wrapped. + * @param color The color to use for wrapping. + * @return The original string wrapped with the color tag. + */ +public infix fun String.withColor(color: String): String = "<$color>$this" + /*** * Encodes the specified byte array into a String using the [Base64] encoding scheme. * @receiver String to encode. diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt index 1b24f813..f80dd207 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt @@ -1,5 +1,7 @@ package com.github.rushyverse.api.extension +import com.github.rushyverse.api.utils.randomString +import io.kotest.matchers.shouldBe import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.assertThrows @@ -124,4 +126,34 @@ class StringExtTest { } } + + @Nested + inner class WithColor { + + @ParameterizedTest + @ValueSource( + strings = [ + "", + " ", + "red" + ] + ) + fun `should wrap non empty string`(value: String) { + val string = randomString() + string withColor value shouldBe "<$value>$string" + } + + @ParameterizedTest + @ValueSource( + strings = [ + "", + " ", + "red" + ] + ) + fun `should wrap empty string`(value: String) { + "" withColor value shouldBe "<$value>" + } + + } } From 4df92366ed0eb8605e4cc6179a7ee12584d1f839 Mon Sep 17 00:00:00 2001 From: Distractic Date: Fri, 4 Aug 2023 17:26:06 +0200 Subject: [PATCH 118/143] fix: Lazy inject for koin --- .../github/rushyverse/api/koin/CraftContext.kt | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/koin/CraftContext.kt b/src/main/kotlin/com/github/rushyverse/api/koin/CraftContext.kt index d1518906..09cd1e76 100644 --- a/src/main/kotlin/com/github/rushyverse/api/koin/CraftContext.kt +++ b/src/main/kotlin/com/github/rushyverse/api/koin/CraftContext.kt @@ -7,9 +7,12 @@ import org.koin.core.KoinApplication import org.koin.core.context.KoinContext import org.koin.core.error.KoinAppAlreadyStartedException import org.koin.core.module.Module +import org.koin.core.qualifier.Qualifier import org.koin.dsl.KoinAppDeclaration import org.koin.dsl.ModuleDeclaration import org.koin.dsl.module +import org.koin.mp.KoinPlatformTools +import kotlin.collections.set /** * Wrapper for [org.koin.dsl.module] that immediately loads the module for the current [Koin] instance. @@ -31,7 +34,10 @@ public fun loadModule( * shared instances between plugins. * @returnA lazy delegate of type T representing the injected instance. */ -public inline fun inject(): Lazy = CraftContext.get(ID_API).inject() +public inline fun inject( + qualifier: Qualifier? = null, + mode: LazyThreadSafetyMode = KoinPlatformTools.defaultLazyMode(), +): Lazy = lazy(mode) { CraftContext.get(ID_API).get(qualifier) } /** * Injects an instance of the specified type T from the Koin context defined for the [id]. @@ -42,8 +48,13 @@ public inline fun inject(): Lazy = CraftContext.get(ID_API) * @param idFallback The id of the memory container to retrieve the instance from if the first one is not found. * @return A lazy delegate of type T representing the injected instance. */ -public inline fun inject(id: String, idFallback: String = ID_API): Lazy = lazy { - CraftContext.get(id).getOrNull() ?: CraftContext.get(idFallback).get() +public inline fun inject( + id: String, + idFallback: String = ID_API, + qualifier: Qualifier? = null, + mode: LazyThreadSafetyMode = KoinPlatformTools.defaultLazyMode(), +): Lazy = lazy(mode) { + CraftContext.get(id).getOrNull(qualifier) ?: CraftContext.get(idFallback).get(qualifier) } /** From 3d4d80951621fcfc0e455a6665f756f7c9b73ab5 Mon Sep 17 00:00:00 2001 From: Distractic <46402441+Distractic@users.noreply.github.com> Date: Sun, 6 Aug 2023 13:02:05 +0200 Subject: [PATCH 119/143] chore(sonar): Add quality analyze (#66) --- .github/workflows/Check.yml | 32 -- .github/workflows/check-branch.yml | 34 ++ .github/workflows/sonar.yml | 38 ++ build.gradle.kts | 24 + config/detekt/detekt.yml | 776 +++++++++++++++++++++++++++++ sonar-project.properties | 11 + 6 files changed, 883 insertions(+), 32 deletions(-) delete mode 100644 .github/workflows/Check.yml create mode 100644 .github/workflows/check-branch.yml create mode 100644 .github/workflows/sonar.yml create mode 100644 config/detekt/detekt.yml create mode 100644 sonar-project.properties diff --git a/.github/workflows/Check.yml b/.github/workflows/Check.yml deleted file mode 100644 index f128840e..00000000 --- a/.github/workflows/Check.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Check - -on: - push: - paths-ignore: - - '**.md' - -concurrency: - group: check-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout project - uses: actions/checkout@v3 - - - name: Initialization - uses: ./.github/actions/init - with: - jdk: 17 - - - name: Build - uses: gradle/gradle-build-action@v2.4.0 - with: - arguments: build -x test - - - name: Test - uses: gradle/gradle-build-action@v2.4.0 - with: - arguments: test diff --git a/.github/workflows/check-branch.yml b/.github/workflows/check-branch.yml new file mode 100644 index 00000000..5f3aa59e --- /dev/null +++ b/.github/workflows/check-branch.yml @@ -0,0 +1,34 @@ +name: Check + +on: + push: + branches-ignore: + - main + paths-ignore: + - '**.md' + +concurrency: + group: check-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout project + uses: actions/checkout@v3 + + - name: Initialization + uses: ./.github/actions/init + with: + jdk: 17 + + - name: Build + uses: gradle/gradle-build-action@v2.7.0 + with: + arguments: build -x test + + - name: Test + uses: gradle/gradle-build-action@v2.4.0 + with: + arguments: test diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml new file mode 100644 index 00000000..8f18b0b8 --- /dev/null +++ b/.github/workflows/sonar.yml @@ -0,0 +1,38 @@ +name: SonarCloud + +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] + +concurrency: + group: sonar-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build and analyze + runs-on: ubuntu-latest + steps: + - name: Checkout project + uses: actions/checkout@v3 + + - name: Initialization + uses: ./.github/actions/init + with: + jdk: 17 + + - name: Build + uses: gradle/gradle-build-action@v2.7.0 + env: + DETEKT_IGNORE_FAILURES: true + with: + arguments: build detekt test jacocoTestReport + + - name: SonarCloud Scan + uses: SonarSource/sonarcloud-github-action@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/build.gradle.kts b/build.gradle.kts index 9afb39da..874edb67 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,8 +5,24 @@ plugins { embeddedKotlin("plugin.serialization") id("org.jetbrains.dokka") version "1.8.20" id("com.github.johnrengelman.shadow") version "8.1.1" + id("io.gitlab.arturbosch.detekt") version "1.23.1" `maven-publish` `java-library` + jacoco +} + +detekt { + // Allows having different behavior for CI. + // When building a branch, we want to fail the build if detekt fails. + // When building a PR, we want to ignore failures to report them in sonar. + val envIgnoreFailures = System.getenv("DETEKT_IGNORE_FAILURES")?.toBooleanStrictOrNull() ?: false + ignoreFailures = envIgnoreFailures + + config.from(file("config/detekt/detekt.yml")) +} + +jacoco { + reportsDirectory.set(file("${layout.buildDirectory.get()}/reports/jacoco")) } repositories { @@ -129,6 +145,14 @@ tasks { shadowJar { archiveClassifier.set("") } + + jacocoTestReport { + reports { + xml.required.set(true) + html.required.set(true) + csv.required.set(false) + } + } } val deleteDokkaOutputDir by tasks.register("deleteDokkaOutputDirectory") { diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml new file mode 100644 index 00000000..e25786bc --- /dev/null +++ b/config/detekt/detekt.yml @@ -0,0 +1,776 @@ +build: + maxIssues: 0 + excludeCorrectable: false + weights: + # complexity: 2 + # LongParameterList: 1 + # style: 1 + # comments: 1 + +config: + validation: true + warningsAsErrors: false + checkExhaustiveness: false + # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]' + excludes: '' + +processors: + active: true + exclude: + - 'DetektProgressListener' + # - 'KtFileCountProcessor' + # - 'PackageCountProcessor' + # - 'ClassCountProcessor' + # - 'FunctionCountProcessor' + # - 'PropertyCountProcessor' + # - 'ProjectComplexityProcessor' + # - 'ProjectCognitiveComplexityProcessor' + # - 'ProjectLLOCProcessor' + # - 'ProjectCLOCProcessor' + # - 'ProjectLOCProcessor' + # - 'ProjectSLOCProcessor' + # - 'LicenseHeaderLoaderExtension' + +console-reports: + active: true + exclude: + - 'ProjectStatisticsReport' + - 'ComplexityReport' + - 'NotificationReport' + - 'FindingsReport' + - 'FileBasedFindingsReport' + # - 'LiteFindingsReport' + +output-reports: + active: true +# exclude: + # - 'TxtOutputReport' + # - 'XmlOutputReport' + # - 'HtmlOutputReport' + # - 'MdOutputReport' + # - 'SarifOutputReport' + +comments: + active: true + AbsentOrWrongFileLicense: + active: false + licenseTemplateFile: 'license.template' + licenseTemplateIsRegex: false + CommentOverPrivateFunction: + active: false + CommentOverPrivateProperty: + active: false + DeprecatedBlockTag: + active: false + EndOfSentenceFormat: + active: false + endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' + KDocReferencesNonPublicProperty: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + OutdatedDocumentation: + active: false + matchTypeParameters: true + matchDeclarationsOrder: true + allowParamOnConstructorProperties: false + excludes: + - '**/*Application*' + UndocumentedPublicClass: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + searchInNestedClass: true + searchInInnerClass: true + searchInInnerObject: true + searchInInnerInterface: true + searchInProtectedClass: false + UndocumentedPublicFunction: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + searchProtectedFunction: false + UndocumentedPublicProperty: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + searchProtectedProperty: false + +complexity: + active: true + CognitiveComplexMethod: + active: false + threshold: 15 + ComplexCondition: + active: true + threshold: 4 + ComplexInterface: + active: false + threshold: 10 + includeStaticDeclarations: false + includePrivateDeclarations: false + ignoreOverloaded: false + CyclomaticComplexMethod: + active: true + threshold: 15 + ignoreSingleWhenExpression: false + ignoreSimpleWhenEntries: false + ignoreNestingFunctions: false + nestingFunctions: + - 'also' + - 'apply' + - 'forEach' + - 'isNotNull' + - 'ifNull' + - 'let' + - 'run' + - 'use' + - 'with' + LabeledExpression: + active: false + ignoredLabels: [] + LargeClass: + active: true + threshold: 600 + LongMethod: + active: false + threshold: 60 + LongParameterList: + active: false + functionThreshold: 6 + constructorThreshold: 7 + ignoreDefaultParameters: false + ignoreDataClasses: true + ignoreAnnotatedParameter: [] + MethodOverloading: + active: false + threshold: 6 + NamedArguments: + active: false + threshold: 3 + ignoreArgumentsMatchingNames: false + NestedBlockDepth: + active: true + threshold: 4 + NestedScopeFunctions: + active: false + threshold: 1 + functions: + - 'kotlin.apply' + - 'kotlin.run' + - 'kotlin.with' + - 'kotlin.let' + - 'kotlin.also' + ReplaceSafeCallChainWithRun: + active: true + StringLiteralDuplication: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + threshold: 3 + ignoreAnnotation: true + excludeStringsWithLessThan5Characters: true + ignoreStringsRegex: '$^' + TooManyFunctions: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + thresholdInFiles: 11 + thresholdInClasses: 11 + thresholdInInterfaces: 11 + thresholdInObjects: 11 + thresholdInEnums: 11 + ignoreDeprecated: false + ignorePrivate: false + ignoreOverridden: false + +coroutines: + active: true + GlobalCoroutineUsage: + active: true + InjectDispatcher: + active: true + dispatcherNames: + - 'IO' + - 'Default' + - 'Unconfined' + RedundantSuspendModifier: + active: true + SleepInsteadOfDelay: + active: true + SuspendFunSwallowedCancellation: + active: true + SuspendFunWithCoroutineScopeReceiver: + active: true + SuspendFunWithFlowReturnType: + active: true + +empty-blocks: + active: true + EmptyCatchBlock: + active: true + allowedExceptionNameRegex: '_|(ignore|expected).*' + EmptyClassBlock: + active: true + EmptyDefaultConstructor: + active: true + EmptyDoWhileBlock: + active: true + EmptyElseBlock: + active: true + EmptyFinallyBlock: + active: true + EmptyForBlock: + active: true + EmptyFunctionBlock: + active: true + ignoreOverridden: false + EmptyIfBlock: + active: true + EmptyInitBlock: + active: true + EmptyKtFile: + active: true + EmptySecondaryConstructor: + active: true + EmptyTryBlock: + active: true + EmptyWhenBlock: + active: true + EmptyWhileBlock: + active: true + +exceptions: + active: true + ExceptionRaisedInUnexpectedLocation: + active: true + methodNames: + - 'equals' + - 'finalize' + - 'hashCode' + - 'toString' + InstanceOfCheckForException: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + NotImplementedDeclaration: + active: false + ObjectExtendsThrowable: + active: true + PrintStackTrace: + active: true + RethrowCaughtException: + active: true + ReturnFromFinally: + active: true + ignoreLabeled: false + SwallowedException: + active: true + ignoredExceptionTypes: + - 'InterruptedException' + - 'MalformedURLException' + - 'NumberFormatException' + - 'ParseException' + allowedExceptionNameRegex: '_|(ignore|expected).*' + ThrowingExceptionFromFinally: + active: true + ThrowingExceptionInMain: + active: false + ThrowingExceptionsWithoutMessageOrCause: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + exceptions: + - 'ArrayIndexOutOfBoundsException' + - 'Exception' + - 'IllegalArgumentException' + - 'IllegalMonitorStateException' + - 'IllegalStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + ThrowingNewInstanceOfSameException: + active: true + TooGenericExceptionCaught: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + exceptionNames: + - 'ArrayIndexOutOfBoundsException' + - 'Error' + - 'Exception' + - 'IllegalMonitorStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + allowedExceptionNameRegex: '_|(ignore|expected).*' + TooGenericExceptionThrown: + active: true + exceptionNames: + - 'Error' + - 'Exception' + - 'RuntimeException' + - 'Throwable' + +naming: + active: true + BooleanPropertyNaming: + active: false + allowedPattern: '^(is|has|are)' + ClassNaming: + active: true + classPattern: '[A-Z][a-zA-Z0-9]*' + ConstructorParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + privateParameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + EnumNaming: + active: true + enumEntryPattern: '[A-Z][_a-zA-Z0-9]*' + ForbiddenClassName: + active: false + forbiddenName: [] + FunctionMaxLength: + active: false + maximumFunctionNameLength: 30 + FunctionMinLength: + active: false + minimumFunctionNameLength: 3 + FunctionNaming: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + functionPattern: '[a-z][a-zA-Z0-9]*' + excludeClassPattern: '$^' + FunctionParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + InvalidPackageDeclaration: + active: true + rootPackage: '' + requireRootInDeclaration: false + LambdaParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*|_' + MatchingDeclarationName: + active: true + mustBeFirst: true + MemberNameEqualsClassName: + active: true + ignoreOverridden: true + NoNameShadowing: + active: true + NonBooleanPropertyPrefixedWithIs: + active: true + ObjectPropertyNaming: + active: true + constantPattern: '[A-Za-z][_A-Za-z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' + PackageNaming: + active: true + packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*' + TopLevelPropertyNaming: + active: true + constantPattern: '[A-Z][_A-Z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' + VariableMaxLength: + active: false + maximumVariableNameLength: 64 + VariableMinLength: + active: false + minimumVariableNameLength: 1 + VariableNaming: + active: true + variablePattern: '[a-z][A-Za-z0-9]*' + privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + +performance: + active: true + ArrayPrimitive: + active: true + CouldBeSequence: + active: false + threshold: 3 + ForEachOnRange: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + SpreadOperator: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + UnnecessaryPartOfBinaryExpression: + active: true + UnnecessaryTemporaryInstantiation: + active: true + +potential-bugs: + active: true + AvoidReferentialEquality: + active: true + forbiddenTypePatterns: + - 'kotlin.String' + CastNullableToNonNullableType: + active: false + CastToNullableType: + active: false + Deprecation: + active: false + DontDowncastCollectionTypes: + active: false + DoubleMutabilityForCollection: + active: true + mutableTypes: + - 'kotlin.collections.MutableList' + - 'kotlin.collections.MutableMap' + - 'kotlin.collections.MutableSet' + - 'java.util.ArrayList' + - 'java.util.LinkedHashSet' + - 'java.util.HashSet' + - 'java.util.LinkedHashMap' + - 'java.util.HashMap' + ElseCaseInsteadOfExhaustiveWhen: + active: true + ignoredSubjectTypes: [] + EqualsAlwaysReturnsTrueOrFalse: + active: true + EqualsWithHashCodeExist: + active: true + ExitOutsideMain: + active: false + ExplicitGarbageCollectionCall: + active: true + HasPlatformType: + active: true + IgnoredReturnValue: + active: true + restrictToConfig: true + returnValueAnnotations: + - 'CheckResult' + - '*.CheckResult' + - 'CheckReturnValue' + - '*.CheckReturnValue' + ignoreReturnValueAnnotations: + - 'CanIgnoreReturnValue' + - '*.CanIgnoreReturnValue' + returnValueTypes: + - 'kotlin.sequences.Sequence' + - 'kotlinx.coroutines.flow.*Flow' + - 'java.util.stream.*Stream' + ignoreFunctionCall: [] + ImplicitDefaultLocale: + active: true + ImplicitUnitReturnType: + active: false + allowExplicitReturnType: true + InvalidRange: + active: true + IteratorHasNextCallsNextMethod: + active: true + IteratorNotThrowingNoSuchElementException: + active: true + LateinitUsage: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + ignoreOnClassesPattern: '' + MapGetWithNotNullAssertionOperator: + active: true + MissingPackageDeclaration: + active: false + excludes: ['**/*.kts'] + NullCheckOnMutableProperty: + active: false + NullableToStringCall: + active: false + UnconditionalJumpStatementInLoop: + active: false + UnnecessaryNotNullCheck: + active: false + UnnecessaryNotNullOperator: + active: true + UnnecessarySafeCall: + active: true + UnreachableCatchBlock: + active: true + UnreachableCode: + active: true + UnsafeCallOnNullableType: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + UnsafeCast: + active: true + UnusedUnaryOperator: + active: true + UselessPostfixExpression: + active: true + WrongEqualsTypeParameter: + active: true + +style: + active: true + AlsoCouldBeApply: + active: false + BracesOnIfStatements: + active: false + singleLine: 'never' + multiLine: 'always' + BracesOnWhenStatements: + active: false + singleLine: 'necessary' + multiLine: 'consistent' + CanBeNonNullable: + active: false + CascadingCallWrapping: + active: false + includeElvis: true + ClassOrdering: + active: false + CollapsibleIfStatements: + active: false + DataClassContainsFunctions: + active: false + conversionFunctionPrefix: + - 'to' + allowOperators: false + DataClassShouldBeImmutable: + active: false + DestructuringDeclarationWithTooManyEntries: + active: true + maxDestructuringEntries: 3 + DoubleNegativeLambda: + active: false + negativeFunctions: + - reason: 'Use `takeIf` instead.' + value: 'takeUnless' + - reason: 'Use `all` instead.' + value: 'none' + negativeFunctionNameParts: + - 'not' + - 'non' + EqualsNullCall: + active: true + EqualsOnSignatureLine: + active: false + ExplicitCollectionElementAccessMethod: + active: false + ExplicitItLambdaParameter: + active: true + ExpressionBodySyntax: + active: false + includeLineWrapping: false + ForbiddenAnnotation: + active: false + annotations: + - reason: 'it is a java annotation. Use `Suppress` instead.' + value: 'java.lang.SuppressWarnings' + - reason: 'it is a java annotation. Use `kotlin.Deprecated` instead.' + value: 'java.lang.Deprecated' + - reason: 'it is a java annotation. Use `kotlin.annotation.MustBeDocumented` instead.' + value: 'java.lang.annotation.Documented' + - reason: 'it is a java annotation. Use `kotlin.annotation.Target` instead.' + value: 'java.lang.annotation.Target' + - reason: 'it is a java annotation. Use `kotlin.annotation.Retention` instead.' + value: 'java.lang.annotation.Retention' + - reason: 'it is a java annotation. Use `kotlin.annotation.Repeatable` instead.' + value: 'java.lang.annotation.Repeatable' + - reason: 'Kotlin does not support @Inherited annotation, see https://youtrack.jetbrains.com/issue/KT-22265' + value: 'java.lang.annotation.Inherited' + ForbiddenComment: + active: true + values: + - 'FIXME:' + - 'STOPSHIP:' + - 'TODO:' + allowedPatterns: '' + customMessage: '' + ForbiddenImport: + active: false + imports: [] + forbiddenPatterns: '' + ForbiddenMethodCall: + active: false + methods: + - reason: 'print does not allow you to configure the output stream. Use a logger instead.' + value: 'kotlin.io.print' + - reason: 'println does not allow you to configure the output stream. Use a logger instead.' + value: 'kotlin.io.println' + ForbiddenSuppress: + active: false + rules: [] + ForbiddenVoid: + active: true + ignoreOverridden: false + ignoreUsageInGenerics: false + FunctionOnlyReturningConstant: + active: true + ignoreOverridableFunction: true + ignoreActualFunction: true + excludedFunctions: [] + LoopWithTooManyJumpStatements: + active: true + maxJumpCount: 1 + MagicNumber: + active: true + ignoreAnnotated: + - org.springframework.core.annotation.Order + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts'] + ignoreNumbers: + - '-1' + - '0' + - '1' + - '2' + ignoreHashCodeFunction: true + ignorePropertyDeclaration: false + ignoreLocalVariableDeclaration: false + ignoreConstantDeclaration: true + ignoreCompanionObjectPropertyDeclaration: true + ignoreAnnotation: false + ignoreNamedArgument: true + ignoreEnums: false + ignoreRanges: false + ignoreExtensionFunctions: true + MandatoryBracesLoops: + active: true + MaxChainedCallsOnSameLine: + active: false + maxChainedCalls: 5 + MaxLineLength: + active: true + maxLineLength: 120 + excludePackageStatements: true + excludeImportStatements: true + excludeCommentStatements: false + excludeRawStrings: true + MayBeConst: + active: true + ModifierOrder: + active: true + MultilineLambdaItParameter: + active: false + MultilineRawStringIndentation: + active: false + indentSize: 4 + NestedClassesVisibility: + active: true + NewLineAtEndOfFile: + active: true + NoTabs: + active: false + NullableBooleanCheck: + active: true + ObjectLiteralToLambda: + active: true + OptionalAbstractKeyword: + active: true + OptionalUnit: + active: true + OptionalWhenBraces: + active: true + PreferToOverPairSyntax: + active: true + ProtectedMemberInFinalClass: + active: true + RedundantExplicitType: + active: false + RedundantHigherOrderMapUsage: + active: true + RedundantVisibilityModifierRule: + active: false + ReturnCount: + active: false + max: 2 + excludedFunctions: + - 'equals' + excludeLabeled: false + excludeReturnFromLambda: true + excludeGuardClauses: false + SafeCast: + active: true + SerialVersionUIDInSerializableClass: + active: true + SpacingBetweenPackageAndImports: + active: true + StringShouldBeRawString: + active: true + maxEscapedCharacterCount: 2 + ignoredCharacters: [] + ThrowsCount: + active: true + max: 2 + excludeGuardClauses: false + TrailingWhitespace: + active: false + TrimMultilineRawString: + active: false + UnderscoresInNumericLiterals: + active: false + acceptableLength: 4 + allowNonStandardGrouping: false + UnnecessaryAbstractClass: + active: true + UnnecessaryAnnotationUseSiteTarget: + active: false + UnnecessaryApply: + active: true + UnnecessaryBackticks: + active: true + UnnecessaryFilter: + active: true + UnnecessaryInheritance: + active: true + UnnecessaryInnerClass: + active: false + UnnecessaryLet: + active: true + UnnecessaryParentheses: + active: true + allowForUnclearPrecedence: true + UntilInsteadOfRangeTo: + active: false + UnusedImports: + active: true + UnusedParameter: + active: true + allowedNames: 'ignored|expected' + UnusedPrivateClass: + active: true + UnusedPrivateMember: + active: true + allowedNames: '' + UnusedPrivateProperty: + active: true + allowedNames: '_|ignored|expected|serialVersionUID' + UseAnyOrNoneInsteadOfFind: + active: true + UseArrayLiteralsInAnnotations: + active: true + UseCheckNotNull: + active: true + UseCheckOrError: + active: true + UseDataClass: + active: false + allowVars: false + UseEmptyCounterpart: + active: false + UseIfEmptyOrIfBlank: + active: false + UseIfInsteadOfWhen: + active: false + ignoreWhenContainingVariableDeclaration: false + UseIsNullOrEmpty: + active: true + UseOrEmpty: + active: true + UseRequire: + active: true + UseRequireNotNull: + active: true + UseSumOfInsteadOfFlatMapSize: + active: true + UselessCallOnNotNull: + active: true + UtilityClassWithPublicConstructor: + active: true + VarCouldBeVal: + active: true + ignoreLateinitVar: false + WildcardImport: + active: false + excludeImports: + - 'java.util.*' diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 00000000..55a18efd --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,11 @@ +sonar.projectKey=Rushyverse_api +sonar.organization=rushyverse +sonar.projectName=${project.name} +sonar.projectVersion=${project.version} +sonar.sources=src/main +sonar.tests=src/test +sonar.kotlin.detekt.reportPaths=build/reports/detekt/detekt.xml +sonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml +sonar.sourceEncoding=UTF-8 +sonar.kotlin.threads=4 +sonar.verbose=true From 07c86357a6d8ac0f65c67ce2643d620b3ca69504 Mon Sep 17 00:00:00 2001 From: Distractic <46402441+Distractic@users.noreply.github.com> Date: Sun, 6 Aug 2023 14:16:57 +0200 Subject: [PATCH 120/143] feat: Serializer for Itemstack (#64) --- .../com/github/rushyverse/api/Plugin.kt | 36 ++- .../rushyverse/api/extension/_Component.kt | 34 +-- .../rushyverse/api/extension/_String.kt | 13 +- .../api/serializer/ComponentSerializer.kt | 30 +++ .../api/serializer/DyeColorSerializer.kt | 8 + .../api/serializer/EnchantmentSerializer.kt | 36 +++ .../api/serializer/EnumSerializer.kt | 51 ++++ .../api/serializer/ItemFlagSerializer.kt | 8 + .../api/serializer/ItemStackSerializer.kt | 228 +++++++++++++++++ .../api/serializer/LocationSerializer.kt | 6 +- .../api/serializer/MaterialSerializer.kt | 8 + .../api/serializer/NamespacedSerializer.kt | 45 ++++ .../api/serializer/PatternSerializer.kt | 58 +++++ .../api/serializer/PatternTypeSerializer.kt | 32 +++ .../api/serializer/RangeDoubleSerializer.kt | 2 +- .../api/serializer/ComponentSerializerTest.kt | 230 ++++++++++++++++++ .../api/serializer/DyeColorSerializerTest.kt | 67 +++++ .../serializer/EnchantmentSerializerTest.kt | 157 ++++++++++++ .../api/serializer/EnumSerializerTest.kt | 70 ++++++ .../api/serializer/ItemFlagSerializerTest.kt | 67 +++++ .../api/serializer/ItemStackSerializerTest.kt | 78 ++++++ .../api/serializer/MaterialSerializerTest.kt | 67 +++++ .../serializer/NamespacedSerializerTest.kt | 95 ++++++++ .../api/serializer/PatternSerializerTest.kt | 85 +++++++ .../serializer/PatternTypeSerializerTest.kt | 79 ++++++ .../serializer/RangeDoubleSerializerTest.kt | 73 ++++++ .../github/rushyverse/api/utils/Generator.kt | 13 +- .../api/world/sphere/SphereAreaTest.kt | 1 - 28 files changed, 1649 insertions(+), 28 deletions(-) create mode 100644 src/main/kotlin/com/github/rushyverse/api/serializer/ComponentSerializer.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/serializer/DyeColorSerializer.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/serializer/EnchantmentSerializer.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/serializer/EnumSerializer.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/serializer/ItemFlagSerializer.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/serializer/ItemStackSerializer.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/serializer/MaterialSerializer.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/serializer/NamespacedSerializer.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/serializer/PatternSerializer.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/serializer/PatternTypeSerializer.kt create mode 100644 src/test/kotlin/com/github/rushyverse/api/serializer/ComponentSerializerTest.kt create mode 100644 src/test/kotlin/com/github/rushyverse/api/serializer/DyeColorSerializerTest.kt create mode 100644 src/test/kotlin/com/github/rushyverse/api/serializer/EnchantmentSerializerTest.kt create mode 100644 src/test/kotlin/com/github/rushyverse/api/serializer/EnumSerializerTest.kt create mode 100644 src/test/kotlin/com/github/rushyverse/api/serializer/ItemFlagSerializerTest.kt create mode 100644 src/test/kotlin/com/github/rushyverse/api/serializer/ItemStackSerializerTest.kt create mode 100644 src/test/kotlin/com/github/rushyverse/api/serializer/MaterialSerializerTest.kt create mode 100644 src/test/kotlin/com/github/rushyverse/api/serializer/NamespacedSerializerTest.kt create mode 100644 src/test/kotlin/com/github/rushyverse/api/serializer/PatternSerializerTest.kt create mode 100644 src/test/kotlin/com/github/rushyverse/api/serializer/PatternTypeSerializerTest.kt create mode 100644 src/test/kotlin/com/github/rushyverse/api/serializer/RangeDoubleSerializerTest.kt diff --git a/src/main/kotlin/com/github/rushyverse/api/Plugin.kt b/src/main/kotlin/com/github/rushyverse/api/Plugin.kt index 20433aee..c878250a 100644 --- a/src/main/kotlin/com/github/rushyverse/api/Plugin.kt +++ b/src/main/kotlin/com/github/rushyverse/api/Plugin.kt @@ -1,6 +1,10 @@ package com.github.rushyverse.api +import com.charleskorn.kaml.Yaml +import com.charleskorn.kaml.YamlConfiguration import com.github.rushyverse.api.APIPlugin.Companion.BUNDLE_API +import com.github.rushyverse.api.configuration.reader.IFileReader +import com.github.rushyverse.api.configuration.reader.YamlFileReader import com.github.rushyverse.api.extension.registerListener import com.github.rushyverse.api.koin.CraftContext import com.github.rushyverse.api.koin.loadModule @@ -9,11 +13,14 @@ import com.github.rushyverse.api.listener.VillagerListener import com.github.rushyverse.api.player.Client import com.github.rushyverse.api.player.ClientManager import com.github.rushyverse.api.player.ClientManagerImpl -import com.github.rushyverse.api.player.scoreboard.ScoreboardManager +import com.github.rushyverse.api.serializer.* import com.github.rushyverse.api.translation.ResourceBundleTranslationProvider import com.github.rushyverse.api.translation.SupportedLanguage import com.github.rushyverse.api.translation.registerResourceBundleForSupportedLocales import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.SerializersModuleBuilder +import kotlinx.serialization.modules.contextual import org.bukkit.entity.Player import org.koin.core.module.Module import org.koin.dsl.bind @@ -55,6 +62,33 @@ public abstract class Plugin : SuspendingJavaPlugin() { super.onDisableAsync() } + /** + * Create a new instance of yaml reader. + * @return The instance of the yaml reader. + */ + protected open fun createYamlReader( + configuration: YamlConfiguration = YamlConfiguration(), + serializerModuleBuilder: SerializersModuleBuilder.() -> Unit = {}, + ): IFileReader { + val yaml = Yaml( + serializersModule = SerializersModule { + contextual(ComponentSerializer) + contextual(DyeColorSerializer) + contextual(EnchantmentSerializer) + contextual(ItemStackSerializer) + contextual(LocationSerializer) + contextual(MaterialSerializer) + contextual(NamespacedSerializer) + contextual(PatternSerializer) + contextual(PatternTypeSerializer) + contextual(RangeDoubleSerializer) + serializerModuleBuilder() + }, + configuration = configuration, + ) + return YamlFileReader(this, yaml) + } + /** * Create a new instance of a client. * @param player Player linked to the client. diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Component.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Component.kt index 60d73bbc..1af8ad3a 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_Component.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Component.kt @@ -2,10 +2,20 @@ package com.github.rushyverse.api.extension import net.kyori.adventure.text.* import net.kyori.adventure.text.format.TextDecoration +import net.kyori.adventure.text.minimessage.MiniMessage +import net.kyori.adventure.text.minimessage.tag.standard.StandardTags import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer import kotlin.contracts.InvocationKind import kotlin.contracts.contract +/** + * MiniMessage instance to serialize components with strict mode. + */ +private val MINI_MESSAGE_STRICT: MiniMessage = MiniMessage.builder() + .strict(true) + .tags(StandardTags.defaults()) + .build() + /** * Creates a text component using a builder. * @param builder Function to build the component with the component builder. @@ -221,20 +231,10 @@ public fun Component.undefineDecorations(): Component = undefineBold().undefineItalic().undefineUnderlined().undefineStrikethrough().undefineObfuscated() /** - * Create a new component using the string content. - * @receiver String to transform. - * @param extractUrls If true, will extract urls from the string and apply a clickable effect on them. - * @param extractColors If true, will extract colors from the string and apply them to the component. - * @param colorChar The character used to define a color. - * @return A new text component. - */ -public fun String.toComponent( - extractUrls: Boolean = false, - extractColors: Boolean = true, - colorChar: Char = LegacyComponentSerializer.AMPERSAND_CHAR, -): TextComponent = LegacyComponentSerializer.builder().apply { - if (extractUrls) extractUrls() - if (extractColors) character(colorChar) -} - .build() - .deserialize(this) + * Converts the component to its string representation using MiniMessage. + * + * @receiver The component to convert. + * @param mini The MiniMessage instance to use for serialization. + * @return The string representation of the component. + */ +public fun Component.asString(mini: MiniMessage = MINI_MESSAGE_STRICT): String = mini.serialize(this) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt index b523b5da..ec7a13b3 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt @@ -8,10 +8,19 @@ import net.kyori.adventure.text.TextComponent import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.minimessage.MiniMessage import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver +import net.kyori.adventure.text.minimessage.tag.standard.StandardTags import org.bukkit.ChatColor import java.math.BigInteger import java.util.* +/** + * MiniMessage instance to deserialize components without strict mode. + */ +private val MINI_MESSAGE_NON_STRICT: MiniMessage = MiniMessage.builder() + .strict(false) + .tags(StandardTags.defaults()) + .build() + /** * Length of a UUID. */ @@ -214,5 +223,5 @@ public fun String.toFormattedLoreSequence(lineLength: Int = DEFAULT_LORE_LINE_LE * @param tagResolver The tag resolver used to resolve the custom tags. * @return The component created from the string. */ -public fun String.toComponent(vararg tagResolver: TagResolver): Component = - MiniMessage.miniMessage().deserialize(this, *tagResolver) +public fun String.asComponent(vararg tagResolver: TagResolver, miniMessage: MiniMessage = MINI_MESSAGE_NON_STRICT): Component = + miniMessage.deserialize(this, *tagResolver) diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/ComponentSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/ComponentSerializer.kt new file mode 100644 index 00000000..fa655ada --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/ComponentSerializer.kt @@ -0,0 +1,30 @@ +package com.github.rushyverse.api.serializer + +import com.github.rushyverse.api.extension.asComponent +import com.github.rushyverse.api.extension.asString +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import net.kyori.adventure.text.Component + +/** + * Serializer for [Component]. + */ +public object ComponentSerializer : KSerializer { + + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor( + "component", + PrimitiveKind.STRING + ) + + override fun serialize(encoder: Encoder, value: Component) { + encoder.encodeString(value.asString()) + } + + override fun deserialize(decoder: Decoder): Component { + return decoder.decodeString().asComponent() + } +} diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/DyeColorSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/DyeColorSerializer.kt new file mode 100644 index 00000000..4672fd96 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/DyeColorSerializer.kt @@ -0,0 +1,8 @@ +package com.github.rushyverse.api.serializer + +import org.bukkit.DyeColor + +/** + * Serializer for [DyeColor]. + */ +public object DyeColorSerializer : EnumSerializer("dyeColor", DyeColor.entries) diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/EnchantmentSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/EnchantmentSerializer.kt new file mode 100644 index 00000000..ff41623b --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/EnchantmentSerializer.kt @@ -0,0 +1,36 @@ +package com.github.rushyverse.api.serializer + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import org.bukkit.NamespacedKey +import org.bukkit.enchantments.Enchantment + +/** + * Serializer for [Enchantment]. + */ +public object EnchantmentSerializer : KSerializer { + + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor( + "enchantment", + PrimitiveKind.STRING + ) + + override fun serialize(encoder: Encoder, value: Enchantment) { + NamespacedSerializer.serialize(encoder, value.key) + } + + override fun deserialize(decoder: Decoder): Enchantment { + val key = NamespacedSerializer.deserialize(decoder) as NamespacedKey + return Enchantment.getByKey(key) + ?: throw SerializationException( + "Unable to find enchantment with namespaced key: $key. Valid enchantments are: ${ + Enchantment.values().joinToString { it.key.asString() } + }" + ) + } +} diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/EnumSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/EnumSerializer.kt new file mode 100644 index 00000000..6931c8f9 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/EnumSerializer.kt @@ -0,0 +1,51 @@ +package com.github.rushyverse.api.serializer + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import org.yaml.snakeyaml.serializer.SerializerException +import kotlin.enums.EnumEntries + +/** + * EnumSerializer is a serializer used to serialize and deserialize enum values. + * + * @param T The type of the enum class. + * @property values The list of enum values to be serialized or deserialized. + * @property descriptor The serial descriptor for the enum serializer. + * @constructor Creates an instance of EnumSerializer with the given serial name and enum values. + */ +public open class EnumSerializer>( + serialName: String, + private val values: EnumEntries +) : KSerializer { + + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(serialName, PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: T) { + encoder.encodeString(value.name) + } + + override fun deserialize(decoder: Decoder): T { + return findEnumValue(decoder.decodeString()) + } + + /** + * Finds the matching enum value for a given decoded string. + * Will transform all spaces to underscore and uppercase all letters. + * So for example, "foo bar" will be transformed to "FOO_BAR". + * If no matching enum value is found, an [SerializerException] will be thrown. + * + * @param decoded The decoded string used to search for the matching enum value. + * @return The matching enum value. + * @throws IllegalArgumentException if no matching enum value is found. + */ + public fun findEnumValue(decoded: String): T { + val name = decoded.uppercase().replace(" ", "_") + return values.firstOrNull { it.name.uppercase() == name } + ?: throw SerializationException("Invalid enum value: $decoded. Valid values are: ${values.joinToString { it.name }}") + } +} diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/ItemFlagSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/ItemFlagSerializer.kt new file mode 100644 index 00000000..81e729ef --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/ItemFlagSerializer.kt @@ -0,0 +1,8 @@ +package com.github.rushyverse.api.serializer + +import org.bukkit.inventory.ItemFlag + +/** + * Serializer for [ItemFlag]. + */ +public object ItemFlagSerializer : EnumSerializer("itemFlag", ItemFlag.entries) diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/ItemStackSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/ItemStackSerializer.kt new file mode 100644 index 00000000..75b74d6b --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/ItemStackSerializer.kt @@ -0,0 +1,228 @@ +package com.github.rushyverse.api.serializer + +import com.destroystokyo.paper.Namespaced +import com.github.rushyverse.api.extension.ItemStack +import com.github.rushyverse.api.extension.getTexturesProperty +import com.github.rushyverse.api.extension.setTextures +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.builtins.nullable +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.* +import net.kyori.adventure.text.Component +import org.bukkit.Bukkit +import org.bukkit.Material +import org.bukkit.block.banner.Pattern +import org.bukkit.enchantments.Enchantment +import org.bukkit.entity.Damageable +import org.bukkit.inventory.ItemFlag +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.meta.BannerMeta +import org.bukkit.inventory.meta.SkullMeta +import java.util.* + +/** + * Serializer for [ItemStack]. + */ +public object ItemStackSerializer : KSerializer { + + private val materialSerializer: KSerializer get() = MaterialSerializer + + private val amountSerializer: KSerializer get() = Int.serializer() + + private val enchantmentsSerializer: KSerializer?> = + MapSerializer(EnchantmentSerializer, Int.serializer()).nullable + + private val unbreakableSerializer: KSerializer = Boolean.serializer().nullable + + private val customModelSerializer: KSerializer = Int.serializer().nullable + + private val destroyableKeysSerializer: KSerializer?> = + ListSerializer(NamespacedSerializer).nullable + + private val placeableKeysSerializer: KSerializer?> get() = destroyableKeysSerializer + + private val displayNameSerializer: KSerializer = ComponentSerializer.nullable + + private val loreSerializer: KSerializer?> = ListSerializer(ComponentSerializer).nullable + + private val durabilitySerializer: KSerializer = Double.serializer().nullable + + private val textureSerializer: KSerializer = String.serializer().nullable + + private val patternsSerializer: KSerializer?> = ListSerializer(PatternSerializer).nullable + + private val flagsSerializer: KSerializer?> = ListSerializer(ItemFlagSerializer).nullable + + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("itemstack") { + element("material", materialSerializer.descriptor) + element("amount", amountSerializer.descriptor) + element("enchantments", enchantmentsSerializer.descriptor) + element("unbreakable", unbreakableSerializer.descriptor) + element("customModel", customModelSerializer.descriptor) + element("destroyableKeys", destroyableKeysSerializer.descriptor) + element("placeableKeys", placeableKeysSerializer.descriptor) + element("displayName", displayNameSerializer.descriptor) + element("lore", loreSerializer.descriptor) + element("durability", durabilitySerializer.descriptor) + element("texture", textureSerializer.descriptor) + element("patterns", patternsSerializer.descriptor) + element("flags", flagsSerializer.descriptor) + } + + override fun serialize(encoder: Encoder, value: ItemStack) { + val itemMeta = if (value.hasItemMeta()) value.itemMeta else null + + encoder.encodeStructure(descriptor) { + encodeSerializableElement(descriptor, 0, materialSerializer, value.type) + encodeSerializableElement(descriptor, 1, amountSerializer, value.amount) + encodeSerializableElement(descriptor, 2, enchantmentsSerializer, value.enchantments) + + if(itemMeta == null) return@encodeStructure + + encodeSerializableElement(descriptor, 3, unbreakableSerializer, itemMeta.isUnbreakable) + encodeSerializableElement(descriptor, 4, customModelSerializer, itemMeta.let { + if(it.hasCustomModelData()) it.customModelData else null + }) + encodeSerializableElement( + descriptor, + 5, + destroyableKeysSerializer, + itemMeta.let { if(it.hasDestroyableKeys()) it.destroyableKeys.toList() else null } + ) + encodeSerializableElement( + descriptor, + 6, + placeableKeysSerializer, + itemMeta.let { if(it.hasPlaceableKeys()) it.placeableKeys.toList() else null } + ) + encodeSerializableElement(descriptor, 7, displayNameSerializer, itemMeta.let { + if(it.hasDisplayName()) it.displayName() else null + }) + encodeSerializableElement(descriptor, 8, loreSerializer, itemMeta.let { + if(it.hasLore()) it.lore() else null + }) + encodeSerializableElement( + descriptor, + 9, + durabilitySerializer, + itemMeta.let { it as? Damageable }?.health + ) + encodeSerializableElement( + descriptor, + 10, + textureSerializer, + itemMeta.let { it as? SkullMeta }?.playerProfile?.getTexturesProperty()?.value + ) + encodeSerializableElement( + descriptor, + 11, + patternsSerializer, + itemMeta.let { it as? BannerMeta }?.patterns + ) + encodeSerializableElement(descriptor, 12, flagsSerializer, itemMeta.itemFlags.toList()) + } + } + + override fun deserialize(decoder: Decoder): ItemStack { + return decoder.decodeStructure(descriptor) { + var material: Material? = null + var amount = 1 + var enchantments: Map? = null + var unbreakable: Boolean? = null + var customModel: Int? = null + var destroyableKeys: Collection? = null + var placeableKeys: Collection? = null + var displayName: Component? = null + var lore: Collection? = null + var flags: Collection? = null + // For item + var durability: Double? = null + // For Skull item + var texture: String? = null + // For banner item + var patterns: List? = null + + if (decodeSequentially()) { + material = decodeSerializableElement(descriptor, 0, materialSerializer) + amount = decodeSerializableElement(descriptor, 1, amountSerializer) + enchantments = decodeSerializableElement(descriptor, 2, enchantmentsSerializer) + unbreakable = decodeSerializableElement(descriptor, 3, unbreakableSerializer) + customModel = decodeSerializableElement(descriptor, 4, customModelSerializer) + destroyableKeys = decodeSerializableElement(descriptor, 5, destroyableKeysSerializer) + placeableKeys = decodeSerializableElement(descriptor, 6, placeableKeysSerializer) + displayName = decodeSerializableElement(descriptor, 7, displayNameSerializer) + lore = decodeSerializableElement(descriptor, 8, loreSerializer) + durability = decodeSerializableElement(descriptor, 9, durabilitySerializer) + texture = decodeSerializableElement(descriptor, 10, textureSerializer) + patterns = decodeSerializableElement(descriptor, 11, patternsSerializer) + flags = decodeSerializableElement(descriptor, 12, flagsSerializer) + } else { + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> material = decodeSerializableElement(descriptor, index, materialSerializer) + 1 -> amount = decodeSerializableElement(descriptor, index, amountSerializer) + 2 -> enchantments = decodeSerializableElement( + descriptor, + index, + enchantmentsSerializer + ) + + 3 -> unbreakable = decodeSerializableElement(descriptor, index, unbreakableSerializer) + 4 -> customModel = decodeSerializableElement(descriptor, index, customModelSerializer) + 5 -> destroyableKeys = decodeSerializableElement(descriptor, index, destroyableKeysSerializer) + 6 -> placeableKeys = decodeSerializableElement(descriptor, index, placeableKeysSerializer) + 7 -> displayName = decodeSerializableElement(descriptor, index, displayNameSerializer) + 8 -> lore = decodeSerializableElement(descriptor, index, loreSerializer) + 9 -> durability = decodeSerializableElement(descriptor, index, durabilitySerializer) + 10 -> texture = decodeSerializableElement(descriptor, index, textureSerializer) + 11 -> patterns = decodeSerializableElement(descriptor, index, patternsSerializer) + 12 -> flags = decodeSerializableElement(descriptor, index, flagsSerializer) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + } + + material ?: throw SerializationException("The field material is missing") + + ItemStack(material) { + this.amount = amount + this.editMeta { + enchantments?.forEach { (enchant, level) -> + it.addEnchant(enchant, level, true) + } + unbreakable?.also(it::setUnbreakable) + customModel?.also(it::setCustomModelData) + destroyableKeys?.also(it::setDestroyableKeys) + placeableKeys?.also(it::setPlaceableKeys) + displayName?.also(it::displayName) + lore?.toList()?.also(it::lore) + flags?.also { itemFlags -> it.addItemFlags(*itemFlags.toTypedArray()) } + + when (it) { + is Damageable -> { + durability?.also(it::damage) + } + + is SkullMeta -> { + texture?.let { texture -> + val profile = Bukkit.createProfile(UUID.randomUUID()) + profile.setTextures(texture) + it.playerProfile = profile + } + } + + is BannerMeta -> { + patterns?.also(it::setPatterns) + } + } + } + } + } + } +} diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/LocationSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/LocationSerializer.kt index ef5cffa3..fc2fe073 100644 --- a/src/main/kotlin/com/github/rushyverse/api/serializer/LocationSerializer.kt +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/LocationSerializer.kt @@ -18,17 +18,17 @@ public object LocationSerializer : KSerializer { /** * Serializer for the coordinates x, y or z. */ - private val coordinateSerializer get() = Double.serializer() + private val coordinateSerializer: KSerializer get() = Double.serializer() /** * Serializer for the rotations yaw or pitch. */ - private val rotationSerializer get() = Float.serializer().nullable + private val rotationSerializer: KSerializer = Float.serializer().nullable /** * Serializer for the world. */ - private val worldSerializer get() = String.serializer().nullable + private val worldSerializer: KSerializer = String.serializer().nullable override val descriptor: SerialDescriptor = buildClassSerialDescriptor("location") { val coordinateSerializer = coordinateSerializer diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/MaterialSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/MaterialSerializer.kt new file mode 100644 index 00000000..0019992d --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/MaterialSerializer.kt @@ -0,0 +1,8 @@ +package com.github.rushyverse.api.serializer + +import org.bukkit.Material + +/** + * Serializer for [Material]. + */ +public object MaterialSerializer : EnumSerializer("material", Material.entries) diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/NamespacedSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/NamespacedSerializer.kt new file mode 100644 index 00000000..5da92163 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/NamespacedSerializer.kt @@ -0,0 +1,45 @@ +package com.github.rushyverse.api.serializer + +import com.destroystokyo.paper.Namespaced +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import org.bukkit.NamespacedKey + +/** + * Serializer for [Namespaced]. + */ +public object NamespacedSerializer : KSerializer { + + private val regexUppercase = Regex("([A-Z])") + + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor( + "namespaced", + PrimitiveKind.STRING + ) + + override fun serialize(encoder: Encoder, value: Namespaced) { + encoder.encodeString(value.namespace + NamespacedKey.DEFAULT_SEPARATOR + value.key) + } + + override fun deserialize(decoder: Decoder): Namespaced { + return decoder.decodeString().let { decodedString -> + val namespacedString = decodedString.split(NamespacedKey.DEFAULT_SEPARATOR).map { + // Replace " " to "_". Example: blue wool -> blue_wool + it.replace(' ', '_') + // Replace "A-Z" to "_a-z". Example: blueWool -> blue_wool + .replace(regexUppercase) { matchResult -> "_${matchResult.value.lowercase()}" } + + } + + if (namespacedString.size == 2) { + NamespacedKey(namespacedString[0], namespacedString[1]) + } else { + NamespacedKey.minecraft(namespacedString[0]) + } + } + } +} diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/PatternSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/PatternSerializer.kt new file mode 100644 index 00000000..f85d2e8d --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/PatternSerializer.kt @@ -0,0 +1,58 @@ +package com.github.rushyverse.api.serializer + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.* +import org.bukkit.DyeColor +import org.bukkit.block.banner.Pattern +import org.bukkit.block.banner.PatternType + +/** + * Serializer for [Pattern]. + */ +public object PatternSerializer : KSerializer { + + private val typeSerializer: PatternTypeSerializer get() = PatternTypeSerializer + + private val dyeColorSerializer: DyeColorSerializer get() = DyeColorSerializer + + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("pattern") { + element("color", dyeColorSerializer.descriptor) + element("type", typeSerializer.descriptor) + } + + override fun serialize(encoder: Encoder, value: Pattern) { + encoder.encodeStructure(descriptor) { + encodeSerializableElement(descriptor, 0, dyeColorSerializer, value.color) + encodeSerializableElement(descriptor, 1, typeSerializer, value.pattern) + } + } + + override fun deserialize(decoder: Decoder): Pattern { + return decoder.decodeStructure(descriptor) { + var color: DyeColor? = null + var type: PatternType? = null + + if (decodeSequentially()) { + color = decodeSerializableElement(descriptor, 0, dyeColorSerializer) + type = decodeSerializableElement(descriptor, 1, typeSerializer) + } else { + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> color = decodeSerializableElement(descriptor, index, dyeColorSerializer) + 1 -> type = decodeSerializableElement(descriptor, index, typeSerializer) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + } + + Pattern( + color ?: throw SerializationException("The field color is missing"), + type ?: throw SerializationException("The field type is missing") + ) + } + } +} diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/PatternTypeSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/PatternTypeSerializer.kt new file mode 100644 index 00000000..7e937e04 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/PatternTypeSerializer.kt @@ -0,0 +1,32 @@ +package com.github.rushyverse.api.serializer + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import org.bukkit.block.banner.PatternType + +/** + * Serializer for [PatternType]. + */ +public object PatternTypeSerializer : KSerializer { + + private val enumSerializer = EnumSerializer("patternTypeEnum", PatternType.entries) + + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor( + "patternType", + PrimitiveKind.STRING + ) + + override fun serialize(encoder: Encoder, value: PatternType) { + encoder.encodeString(value.identifier) + } + + override fun deserialize(decoder: Decoder): PatternType { + val key = decoder.decodeString() + return PatternType.getByIdentifier(key.lowercase()) ?: enumSerializer.findEnumValue(key) + } +} diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/RangeDoubleSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/RangeDoubleSerializer.kt index 83ad753d..7300d305 100644 --- a/src/main/kotlin/com/github/rushyverse/api/serializer/RangeDoubleSerializer.kt +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/RangeDoubleSerializer.kt @@ -15,7 +15,7 @@ public object RangeDoubleSerializer : KSerializer> { /** * Serializer for [Double]. */ - private val doubleSerializer get() = Double.serializer() + private val doubleSerializer: KSerializer get() = Double.serializer() override val descriptor: SerialDescriptor = buildClassSerialDescriptor("range") { val doubleSerializer = doubleSerializer diff --git a/src/test/kotlin/com/github/rushyverse/api/serializer/ComponentSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/serializer/ComponentSerializerTest.kt new file mode 100644 index 00000000..97bdb5f9 --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/serializer/ComponentSerializerTest.kt @@ -0,0 +1,230 @@ +package com.github.rushyverse.api.serializer + +import com.github.rushyverse.api.utils.randomString +import io.kotest.assertions.json.shouldEqualJson +import io.kotest.matchers.shouldBe +import kotlinx.serialization.json.Json +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.format.TextColor +import net.kyori.adventure.text.format.TextDecoration +import org.junit.jupiter.api.Nested +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource +import org.junit.jupiter.params.provider.ValueSource +import kotlin.test.Test + +class ComponentSerializerTest { + + @Nested + inner class Serialize { + + @ParameterizedTest + @ValueSource( + strings = [ + "", + " ", + "My string" + ] + ) + fun `without decoration`(value: String) { + val component = Component.text(value) + Json.encodeToString(ComponentSerializer, component) shouldEqualJson """ + "$value" + """.trimIndent() + } + + @Test + fun `with named color`() { + fun assertColor(color: NamedTextColor) { + val string = randomString() + val colorName = color.toString() + val component = Component.text(string, color) + Json.encodeToString(ComponentSerializer, component) shouldEqualJson """ + "<$colorName>$string" + """.trimIndent() + } + + assertColor(NamedTextColor.AQUA) + assertColor(NamedTextColor.BLACK) + assertColor(NamedTextColor.BLUE) + assertColor(NamedTextColor.DARK_AQUA) + assertColor(NamedTextColor.DARK_BLUE) + } + + @Test + fun `with custom color`() { + fun assertColor(hex: String, color: NamedTextColor? = null) { + val string = randomString() + val component = Component.text(string, TextColor.fromHexString(hex)) + + val expected = if (color != null) { + val colorName = color.toString() + """ + "<$colorName>$string" + """.trimIndent() + } else { + val colorName = hex.lowercase() + """ + "<$colorName>$string" + """.trimIndent() + } + + Json.encodeToString(ComponentSerializer, component) shouldEqualJson expected + } + + assertColor("#e3e3e3") + assertColor("#000000", NamedTextColor.BLACK) + assertColor("#FFFFFF", NamedTextColor.WHITE) + assertColor("#FF0000") + } + + @ParameterizedTest + @EnumSource(TextDecoration::class) + fun `with decoration`(decoration: TextDecoration) { + val string = randomString() + val component = Component.text(string).decorate(decoration) + val decorationName = decoration.toString() + Json.encodeToString(ComponentSerializer, component) shouldEqualJson """ + "<$decorationName>$string" + """.trimIndent() + } + + @Test + fun `with color and decoration`() { + val string = randomString() + val component = Component.text(string, NamedTextColor.AQUA).decorate(TextDecoration.BOLD) + Json.encodeToString(ComponentSerializer, component) shouldEqualJson """ + "$string" + """.trimIndent() + } + + + } + + @Nested + inner class Deserialize { + + @ParameterizedTest + @ValueSource( + strings = [ + "", + " ", + "My string" + ] + ) + fun `without decoration`(value: String) { + Json.decodeFromString( + ComponentSerializer, + """ + "$value" + """.trimIndent() + ) shouldBe Component.text(value) + } + + @Test + fun `with named color`() { + fun assertColor(color: NamedTextColor) { + val string = randomString() + val colorName = color.toString() + val expectedComponent = Component.text(string).color(color) + + // Non strict mode supported + Json.decodeFromString( + ComponentSerializer, + """ + "<$colorName>$string" + """.trimIndent() + ) shouldBe expectedComponent + + // Strict mode supported + Json.decodeFromString( + ComponentSerializer, + """ + "<$colorName>$string" + """.trimIndent() + ) shouldBe expectedComponent + } + + assertColor(NamedTextColor.AQUA) + assertColor(NamedTextColor.BLACK) + assertColor(NamedTextColor.BLUE) + assertColor(NamedTextColor.DARK_AQUA) + assertColor(NamedTextColor.DARK_BLUE) + } + + @Test + fun `with custom color`() { + fun assertColor(hex: String) { + val string = randomString() + val expectedComponent = Component.text(string, TextColor.fromHexString(hex)) + + // Non strict mode supported + Json.decodeFromString( + ComponentSerializer, + """ + "<$hex>$string" + """.trimIndent() + ) shouldBe expectedComponent + + // Strict mode supported + Json.decodeFromString( + ComponentSerializer, + """ + "<$hex>$string" + """.trimIndent() + ) shouldBe expectedComponent + } + + assertColor("#e3e3e3") + assertColor("#000000") + assertColor("#FFFFFF") + assertColor("#FF0000") + } + + @ParameterizedTest + @EnumSource(TextDecoration::class) + fun `with decoration`(decoration: TextDecoration) { + val string = randomString() + val expectedComponent = Component.text(string).decorate(decoration) + val decorationName = decoration.toString() + + // Non strict mode supported + Json.decodeFromString( + ComponentSerializer, + """ + "<$decorationName>$string" + """.trimIndent() + ) shouldBe expectedComponent + + // Strict mode supported + Json.decodeFromString( + ComponentSerializer, + """ + "<$decorationName>$string" + """.trimIndent() + ) shouldBe expectedComponent + } + + @Test + fun `with color and decoration`() { + val string = randomString() + val expectedComponent = Component.text(string, NamedTextColor.AQUA).decorate(TextDecoration.BOLD) + + Json.decodeFromString( + ComponentSerializer, + """ + "$string" + """.trimIndent() + ) shouldBe expectedComponent + + Json.decodeFromString( + ComponentSerializer, + """ + "$string" + """.trimIndent() + ) shouldBe expectedComponent + } + + } +} diff --git a/src/test/kotlin/com/github/rushyverse/api/serializer/DyeColorSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/serializer/DyeColorSerializerTest.kt new file mode 100644 index 00000000..7b3d83e4 --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/serializer/DyeColorSerializerTest.kt @@ -0,0 +1,67 @@ +package com.github.rushyverse.api.serializer + +import com.github.rushyverse.api.utils.randomString +import io.kotest.assertions.json.shouldEqualJson +import io.kotest.matchers.shouldBe +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json +import org.bukkit.DyeColor +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource +import kotlin.test.Test + +class DyeColorSerializerTest { + + @Nested + inner class Serialize { + + @ParameterizedTest + @EnumSource(DyeColor::class) + fun `should use enum name`(value: DyeColor) { + val enumName = value.name + Json.encodeToString(DyeColorSerializer, value) shouldEqualJson """ + "$enumName" + """.trimIndent() + } + } + + @Nested + inner class Deserialize { + + @ParameterizedTest + @EnumSource(DyeColor::class) + fun `should find value with uppercase`(value: DyeColor) { + val enumName = value.name.uppercase() + Json.decodeFromString(DyeColorSerializer, "\"$enumName\"") shouldBe value + } + + @ParameterizedTest + @EnumSource(DyeColor::class) + fun `should find value with lowercase`(value: DyeColor) { + val enumName = value.name.lowercase() + Json.decodeFromString(DyeColorSerializer, "\"$enumName\"") shouldBe value + } + + @ParameterizedTest + @EnumSource(DyeColor::class) + fun `should find value with space`(value: DyeColor) { + val enumName = value.name.replace("_", " ") + Json.decodeFromString(DyeColorSerializer, "\"$enumName\"") shouldBe value + } + + @Test + fun `should throw exception if value is not found`() { + val enumName = randomString() + val exception = assertThrows { + Json.decodeFromString(DyeColorSerializer, "\"$enumName\"") + } + exception.message shouldBe "Invalid enum value: $enumName. Valid values are: ${ + DyeColor.entries.joinToString( + ", " + ) + }" + } + } +} diff --git a/src/test/kotlin/com/github/rushyverse/api/serializer/EnchantmentSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/serializer/EnchantmentSerializerTest.kt new file mode 100644 index 00000000..c33a2db6 --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/serializer/EnchantmentSerializerTest.kt @@ -0,0 +1,157 @@ +package com.github.rushyverse.api.serializer + +import be.seeseemelk.mockbukkit.MockBukkit +import com.github.rushyverse.api.utils.randomString +import io.kotest.assertions.json.shouldEqualJson +import io.kotest.matchers.shouldBe +import io.papermc.paper.enchantments.EnchantmentRarity +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json +import net.kyori.adventure.text.Component +import org.bukkit.NamespacedKey +import org.bukkit.enchantments.Enchantment +import org.bukkit.enchantments.EnchantmentTarget +import org.bukkit.entity.EntityCategory +import org.bukkit.inventory.EquipmentSlot +import org.bukkit.inventory.ItemStack +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test + +class EnchantmentSerializerTest { + + class EnchantmentMock(private val _name: String, namespace: NamespacedKey) : Enchantment(namespace) { + override fun translationKey(): String = error("Not implemented") + override fun getName(): String = _name + override fun getMaxLevel(): Int = error("Not implemented") + override fun getStartLevel(): Int = error("Not implemented") + override fun getItemTarget(): EnchantmentTarget = error("Not implemented") + override fun isTreasure(): Boolean = error("Not implemented") + override fun isCursed(): Boolean = error("Not implemented") + override fun conflictsWith(other: Enchantment): Boolean = error("Not implemented") + override fun canEnchantItem(item: ItemStack): Boolean = error("Not implemented") + override fun displayName(level: Int): Component = error("Not implemented") + override fun isTradeable(): Boolean = error("Not implemented") + override fun isDiscoverable(): Boolean = error("Not implemented") + override fun getRarity(): EnchantmentRarity = error("Not implemented") + override fun getDamageIncrease(level: Int, entityCategory: EntityCategory): Float = + error("Not implemented") + + override fun getActiveSlots(): MutableSet = error("Not implemented") + } + + @BeforeTest + fun onBefore() { + MockBukkit.mock() + Enchantment.values().isEmpty() shouldBe false + } + + @AfterTest + fun onAfter() { + MockBukkit.unmock() + } + + @Nested + inner class Serialize { + + @Test + fun `should use namespace and key`() { + fun assertEnchant(enchant: Enchantment) { + val namespace = enchant.key.namespace + val key = enchant.key.key + Json.encodeToString(EnchantmentSerializer, enchant) shouldEqualJson """ + "$namespace:$key" + """.trimIndent() + } + + Enchantment.values().forEach(::assertEnchant) + } + + @Test + fun `should serialize custom enchantment`() { + val namespace = randomAcceptableNamespace() + val key = randomAcceptableNamespace() + val namespacedKey = NamespacedKey(namespace, key) + val enchantment = EnchantmentMock(key, namespacedKey) + Enchantment.registerEnchantment(enchantment) + + Json.encodeToString(EnchantmentSerializer, enchantment) shouldEqualJson """ + "$namespace:$key" + """.trimIndent() + } + } + + @Nested + inner class Deserialize { + + @Test + fun `should find with only key if minecraft is namespace`() { + val enchant = Enchantment.values().random() + val key = enchant.key.key + val json = """ + "$key" + """.trimIndent() + + Json.decodeFromString(EnchantmentSerializer, json) shouldBe enchant + } + + @Test + fun `should find with namespace and key`() { + val enchant = Enchantment.values().random() + val namespace = enchant.key.namespace + val key = enchant.key.key + val json = """ + "$namespace:$key" + """.trimIndent() + + Json.decodeFromString(EnchantmentSerializer, json) shouldBe enchant + } + + @Test + fun `should find with uppercase instead of underscore`() { + val enchant = Enchantment.FIRE_ASPECT + fun decode(key: String) { + val json = """ + "$key" + """.trimIndent() + + Json.decodeFromString(EnchantmentSerializer, json) shouldBe enchant + } + decode("fire_aspect") + decode("fireAspect") + } + + @Test + fun `should find custom enchantment`() { + val namespace = randomAcceptableNamespace() + val key = randomAcceptableNamespace() + val namespacedKey = NamespacedKey(namespace, key) + val enchantment = EnchantmentMock(key, namespacedKey) + Enchantment.registerEnchantment(enchantment) + + val json = """ + "$namespace:$key" + """.trimIndent() + Json.decodeFromString(EnchantmentSerializer, json) shouldBe enchantment + } + + @Test + fun `should throw if not found`() { + val namespace = randomAcceptableNamespace() + val key = randomAcceptableNamespace() + val json = """ + "$namespace:$key" + """.trimIndent() + + val exception = assertThrows { + Json.decodeFromString(EnchantmentSerializer, json) + } + + exception.message shouldBe "Unable to find enchantment with namespaced key: $namespace:$key. Valid enchantments are: minecraft:impaling, minecraft:thorns, minecraft:piercing, minecraft:fire_protection, minecraft:smite, minecraft:unbreaking, minecraft:swift_sneak, minecraft:feather_falling, minecraft:mending, minecraft:protection, minecraft:respiration, minecraft:projectile_protection, minecraft:knockback, minecraft:fire_aspect, minecraft:luck_of_the_sea, minecraft:lure, minecraft:punch, minecraft:channeling, minecraft:frost_walker, minecraft:sharpness, minecraft:power, minecraft:riptide, minecraft:bane_of_arthropods, minecraft:efficiency, minecraft:fortune, minecraft:looting, minecraft:loyalty, minecraft:silk_touch, minecraft:quick_charge, minecraft:binding_curse, minecraft:aqua_affinity, minecraft:multishot, minecraft:depth_strider, minecraft:vanishing_curse, minecraft:infinity, minecraft:flame, minecraft:blast_protection, minecraft:sweeping" + } + } + + fun randomAcceptableNamespace() = randomString(('a'..'z') + ('0'..'9') + '_') +} diff --git a/src/test/kotlin/com/github/rushyverse/api/serializer/EnumSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/serializer/EnumSerializerTest.kt new file mode 100644 index 00000000..322de8de --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/serializer/EnumSerializerTest.kt @@ -0,0 +1,70 @@ +package com.github.rushyverse.api.serializer + +import com.github.rushyverse.api.utils.randomString +import io.kotest.assertions.json.shouldEqualJson +import io.kotest.matchers.shouldBe +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource +import kotlin.test.Test + +class EnumSerializerTest { + + @Suppress("unused") + enum class TestEnum { + TEST_VALUE1, + TestValue2, + } + + data object TestEnumSerializer : EnumSerializer("testEnum", TestEnum.entries) + + @Nested + inner class Serialize { + + @ParameterizedTest + @EnumSource(TestEnum::class) + fun `should use enum name`(value: TestEnum) { + val enumName = value.name + Json.encodeToString(TestEnumSerializer, value) shouldEqualJson """ + "$enumName" + """.trimIndent() + } + } + + @Nested + inner class Deserialize { + + @ParameterizedTest + @EnumSource(TestEnum::class) + fun `should find value with uppercase`(value: TestEnum) { + val enumName = value.name.uppercase() + Json.decodeFromString(TestEnumSerializer, "\"$enumName\"") shouldBe value + } + + @ParameterizedTest + @EnumSource(TestEnum::class) + fun `should find value with lowercase`(value: TestEnum) { + val enumName = value.name.lowercase() + Json.decodeFromString(TestEnumSerializer, "\"$enumName\"") shouldBe value + } + + @ParameterizedTest + @EnumSource(TestEnum::class) + fun `should find value with space`(value: TestEnum) { + val enumName = value.name.replace("_", " ") + Json.decodeFromString(TestEnumSerializer, "\"$enumName\"") shouldBe value + } + + @Test + fun `should throw exception if value is not found`() { + val enumName = randomString() + val exception = assertThrows { + Json.decodeFromString(TestEnumSerializer, "\"$enumName\"") + } + exception.message shouldBe "Invalid enum value: $enumName. Valid values are: TEST_VALUE1, TestValue2" + } + } +} diff --git a/src/test/kotlin/com/github/rushyverse/api/serializer/ItemFlagSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/serializer/ItemFlagSerializerTest.kt new file mode 100644 index 00000000..2dd55974 --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/serializer/ItemFlagSerializerTest.kt @@ -0,0 +1,67 @@ +package com.github.rushyverse.api.serializer + +import com.github.rushyverse.api.utils.randomString +import io.kotest.assertions.json.shouldEqualJson +import io.kotest.matchers.shouldBe +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json +import org.bukkit.inventory.ItemFlag +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource +import kotlin.test.Test + +class ItemFlagSerializerTest { + + @Nested + inner class Serialize { + + @ParameterizedTest + @EnumSource(ItemFlag::class) + fun `should use enum name`(item: ItemFlag) { + val enumName = item.name + Json.encodeToString(ItemFlagSerializer, item) shouldEqualJson """ + "$enumName" + """.trimIndent() + } + } + + @Nested + inner class Deserialize { + + @ParameterizedTest + @EnumSource(ItemFlag::class) + fun `should find value with uppercase`(value: ItemFlag) { + val enumName = value.name.uppercase() + Json.decodeFromString(ItemFlagSerializer, "\"$enumName\"") shouldBe value + } + + @ParameterizedTest + @EnumSource(ItemFlag::class) + fun `should find value with lowercase`(value: ItemFlag) { + val enumName = value.name.lowercase() + Json.decodeFromString(ItemFlagSerializer, "\"$enumName\"") shouldBe value + } + + @ParameterizedTest + @EnumSource(ItemFlag::class) + fun `should find value with space`(value: ItemFlag) { + val enumName = value.name.replace("_", " ") + Json.decodeFromString(ItemFlagSerializer, "\"$enumName\"") shouldBe value + } + + @Test + fun `should throw exception if value is not found`() { + val enumName = randomString() + val exception = assertThrows { + Json.decodeFromString(ItemFlagSerializer, "\"$enumName\"") + } + exception.message shouldBe "Invalid enum value: $enumName. Valid values are: ${ + ItemFlag.entries.joinToString( + ", " + ) + }" + } + } +} diff --git a/src/test/kotlin/com/github/rushyverse/api/serializer/ItemStackSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/serializer/ItemStackSerializerTest.kt new file mode 100644 index 00000000..670724ff --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/serializer/ItemStackSerializerTest.kt @@ -0,0 +1,78 @@ +package com.github.rushyverse.api.serializer + +import be.seeseemelk.mockbukkit.MockBukkit +import com.github.rushyverse.api.utils.randomEnum +import com.github.rushyverse.api.utils.randomInt +import io.kotest.assertions.json.shouldEqualJson +import kotlinx.serialization.json.Json +import org.bukkit.Material +import org.bukkit.enchantments.Enchantment +import org.bukkit.inventory.ItemStack +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Nested +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test + +class ItemStackSerializerTest { + + @BeforeTest + fun onBefore() { + MockBukkit.mock() + } + + @AfterTest + fun onAfter() { + MockBukkit.unmock() + } + + @Nested + inner class Serialize { + + @Test + fun `with only material`() { + val material = randomEnum() + val amount = randomInt(1, 64) + val itemStack = ItemStack(material, amount) + val json = Json.encodeToString(ItemStackSerializer, itemStack) + json shouldEqualJson """ + { + "material": "${material.name}", + "amount": $amount, + "enchantments": {} + } + """.trimIndent() + } + + @Test + @Disabled // Waiting for https://github.com/MockBukkit/MockBukkit/pull/831 + fun `with enchantments`() { + val itemStack = ItemStack(Material.WOODEN_AXE).apply { + addUnsafeEnchantment(Enchantment.ARROW_DAMAGE, 1) + addUnsafeEnchantment(Enchantment.ARROW_FIRE, 42) + } + val json = Json.encodeToString(ItemStackSerializer, itemStack) + json shouldEqualJson """ + { + "material": "WOODEN_AXE", + "amount": 1, + "enchantments": { + "power": 1, + "flame": 42 + }, + "unbreakable": null, + "customMetaModel": null, + "destroyableKeys": [], + "placeableKeys": null, + "displayName": null, + "lore": null, + "durability": null, + "texture": null, + "patterns": null, + "flags": null + } + """.trimIndent() + } + + } +} diff --git a/src/test/kotlin/com/github/rushyverse/api/serializer/MaterialSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/serializer/MaterialSerializerTest.kt new file mode 100644 index 00000000..3aea51fe --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/serializer/MaterialSerializerTest.kt @@ -0,0 +1,67 @@ +package com.github.rushyverse.api.serializer + +import com.github.rushyverse.api.utils.randomString +import io.kotest.assertions.json.shouldEqualJson +import io.kotest.matchers.shouldBe +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json +import org.bukkit.Material +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource +import kotlin.test.Test + +class MaterialSerializerTest { + + @Nested + inner class Serialize { + + @ParameterizedTest + @EnumSource(Material::class) + fun `should use enum name`(color: Material) { + val enumName = color.name + Json.encodeToString(MaterialSerializer, color) shouldEqualJson """ + "$enumName" + """.trimIndent() + } + } + + @Nested + inner class Deserialize { + + @ParameterizedTest + @EnumSource(Material::class) + fun `should find value with uppercase`(color: Material) { + val enumName = color.name.uppercase() + Json.decodeFromString(MaterialSerializer, "\"$enumName\"") shouldBe color + } + + @ParameterizedTest + @EnumSource(Material::class) + fun `should find value with lowercase`(color: Material) { + val enumName = color.name.lowercase() + Json.decodeFromString(MaterialSerializer, "\"$enumName\"") shouldBe color + } + + @ParameterizedTest + @EnumSource(Material::class) + fun `should find value with space`(color: Material) { + val enumName = color.name.replace("_", " ") + Json.decodeFromString(MaterialSerializer, "\"$enumName\"") shouldBe color + } + + @Test + fun `should throw exception if value is not found`() { + val enumName = randomString() + val exception = assertThrows { + Json.decodeFromString(MaterialSerializer, "\"$enumName\"") + } + exception.message shouldBe "Invalid enum value: $enumName. Valid values are: ${ + Material.entries.joinToString( + ", " + ) + }" + } + } +} diff --git a/src/test/kotlin/com/github/rushyverse/api/serializer/NamespacedSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/serializer/NamespacedSerializerTest.kt new file mode 100644 index 00000000..3d38aca3 --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/serializer/NamespacedSerializerTest.kt @@ -0,0 +1,95 @@ +package com.github.rushyverse.api.serializer + +import be.seeseemelk.mockbukkit.MockBukkit +import com.github.rushyverse.api.utils.randomString +import io.kotest.assertions.json.shouldEqualJson +import io.kotest.matchers.shouldBe +import kotlinx.serialization.json.Json +import org.bukkit.NamespacedKey +import org.bukkit.enchantments.Enchantment +import org.junit.jupiter.api.Nested +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test + +class NamespacedSerializerTest { + + @BeforeTest + fun onBefore() { + MockBukkit.mock() + Enchantment.values().isEmpty() shouldBe false + } + + @AfterTest + fun onAfter() { + MockBukkit.unmock() + } + + @Nested + inner class Serialize { + + @Test + fun `should use namespace and key`() { + val namespace = randomAcceptableNamespace() + val key = randomAcceptableNamespace() + Json.encodeToString(NamespacedSerializer, NamespacedKey(namespace, key)) shouldEqualJson """ + "$namespace:$key" + """.trimIndent() + } + } + + @Nested + inner class Deserialize { + + @Test + fun `should create with minecraft namespace by default if not defined`() { + val key = randomAcceptableNamespace() + val json = """ + "$key" + """.trimIndent() + + Json.decodeFromString(NamespacedSerializer, json) shouldBe NamespacedKey("minecraft", key) + } + + @Test + fun `should create with namespace and key`() { + val namespace = randomAcceptableNamespace() + val key = randomAcceptableNamespace() + val json = """ + "$namespace:$key" + """.trimIndent() + + Json.decodeFromString(NamespacedSerializer, json) shouldBe NamespacedKey(namespace, key) + } + + @Test + fun `should replace uppercase by underscore and lowercase`() { + fun decode(namespace: String?, key: String, expected: NamespacedKey) { + val json = """ + "${if (namespace != null) "$namespace:" else ""}$key" + """.trimIndent() + + Json.decodeFromString(NamespacedSerializer, json) shouldBe expected + } + decode("test", "myKey", NamespacedKey("test", "my_key")) + decode("myNamespace", "myKey", NamespacedKey("my_namespace", "my_key")) + decode(null, "myKey", NamespacedKey.minecraft("my_key")) + } + + @Test + fun `should replace space by underscore`() { + fun decode(namespace: String?, key: String, expected: NamespacedKey) { + val json = """ + "${if (namespace != null) "$namespace:" else ""}$key" + """.trimIndent() + + Json.decodeFromString(NamespacedSerializer, json) shouldBe expected + } + decode("test", "my key", NamespacedKey("test", "my_key")) + decode("my namespace", "my key", NamespacedKey("my_namespace", "my_key")) + decode(null, "my key", NamespacedKey.minecraft("my_key")) + } + } + + fun randomAcceptableNamespace() = randomString(('a'..'z') + ('0'..'9') + '.' + '_' + '-') +} diff --git a/src/test/kotlin/com/github/rushyverse/api/serializer/PatternSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/serializer/PatternSerializerTest.kt new file mode 100644 index 00000000..6e5118de --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/serializer/PatternSerializerTest.kt @@ -0,0 +1,85 @@ +package com.github.rushyverse.api.serializer + +import com.github.rushyverse.api.utils.randomEnum +import io.kotest.assertions.json.shouldEqualJson +import io.kotest.matchers.shouldBe +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json +import org.bukkit.DyeColor +import org.bukkit.block.banner.Pattern +import org.bukkit.block.banner.PatternType +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import kotlin.test.Test + +class PatternSerializerTest { + + @Nested + inner class Serialize { + + @Test + fun `should serialize using type identifier and color name`() { + val dyeColor = randomEnum() + val patternType = randomEnum() + val pattern = Pattern(dyeColor, patternType) + Json.encodeToString(PatternSerializer, pattern) shouldEqualJson """ + { + "color": "${dyeColor.name}", + "type": "${patternType.identifier}" + } + """.trimIndent() + } + } + + @Nested + inner class Deserialize { + + @Test + fun `should throw if color is missing`() { + val patternType = randomEnum() + val json = """ + { + "type": "${patternType.identifier}" + } + """.trimIndent() + assertThrows { + Json.decodeFromString(PatternSerializer, json) + } + } + + @Test + fun `should throw if type is missing`() { + val dyeColor = randomEnum() + val json = """ + { + "color": "${dyeColor.name}" + } + """.trimIndent() + assertThrows { + Json.decodeFromString(PatternSerializer, json) + } + } + + @Test + fun `should deserialize using type and color name`() { + val dyeColor = randomEnum() + val patternType = randomEnum() + + fun decode(color: String, type: String) { + val json = """ + { + "color": "$color", + "type": "$type" + } + """.trimIndent() + val pattern = Json.decodeFromString(PatternSerializer, json) + pattern.color shouldBe dyeColor + pattern.pattern shouldBe patternType + } + + decode(dyeColor.name, patternType.identifier) + decode(dyeColor.name, patternType.name) + } + + } +} diff --git a/src/test/kotlin/com/github/rushyverse/api/serializer/PatternTypeSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/serializer/PatternTypeSerializerTest.kt new file mode 100644 index 00000000..e3fe8d24 --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/serializer/PatternTypeSerializerTest.kt @@ -0,0 +1,79 @@ +package com.github.rushyverse.api.serializer + +import com.github.rushyverse.api.utils.randomString +import io.kotest.assertions.json.shouldEqualJson +import io.kotest.matchers.shouldBe +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json +import org.bukkit.block.banner.PatternType +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource +import kotlin.test.Test + +class PatternTypeSerializerTest { + + @Nested + inner class Serialize { + + @ParameterizedTest + @EnumSource(PatternType::class) + fun `should use identifier`(value: PatternType) { + val identifier = value.identifier.lowercase() + Json.encodeToString(PatternTypeSerializer, value) shouldEqualJson """ + "$identifier" + """.trimIndent() + } + } + + @Nested + inner class Deserialize { + + @ParameterizedTest + @EnumSource(PatternType::class) + fun `should find value with identifier lowercase`(value: PatternType) { + val identifier = value.identifier.lowercase() + Json.decodeFromString(PatternTypeSerializer, "\"$identifier\"") shouldBe value + } + + @ParameterizedTest + @EnumSource(PatternType::class) + fun `should find value with identifier uppercase`(value: PatternType) { + val identifier = value.identifier.uppercase() + Json.decodeFromString(PatternTypeSerializer, "\"$identifier\"") shouldBe value + } + + @ParameterizedTest + @EnumSource(PatternType::class) + fun `should find value with name uppercase`(value: PatternType) { + val enumName = value.name.uppercase() + Json.decodeFromString(PatternTypeSerializer, "\"$enumName\"") shouldBe value + } + + @ParameterizedTest + @EnumSource(PatternType::class) + fun `should find value with name lowercase`(value: PatternType) { + val enumName = value.name.lowercase() + Json.decodeFromString(PatternTypeSerializer, "\"$enumName\"") shouldBe value + } + + @ParameterizedTest + @EnumSource(PatternType::class) + fun `should find value with name space`(value: PatternType) { + val enumName = value.name.replace("_", " ") + Json.decodeFromString(PatternTypeSerializer, "\"$enumName\"") shouldBe value + } + + @Test + fun `should throw exception if value is not found`() { + val enumName = randomString() + val exception = assertThrows { + Json.decodeFromString(PatternTypeSerializer, "\"$enumName\"") + } + exception.message shouldBe "Invalid enum value: $enumName. Valid values are: ${ + PatternType.entries.joinToString(", ") + }" + } + } +} diff --git a/src/test/kotlin/com/github/rushyverse/api/serializer/RangeDoubleSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/serializer/RangeDoubleSerializerTest.kt new file mode 100644 index 00000000..ff48fe40 --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/serializer/RangeDoubleSerializerTest.kt @@ -0,0 +1,73 @@ +package com.github.rushyverse.api.serializer + +import com.github.rushyverse.api.utils.randomDouble +import io.kotest.assertions.json.shouldEqualJson +import io.kotest.matchers.shouldBe +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import kotlin.test.Test + +class RangeDoubleSerializerTest { + + @Nested + inner class Serialize { + + @Test + fun `should serialize using start and end`() { + val start = randomDouble(-100.0, 100.0) + val end = randomDouble(100.0, 200.0) + Json.encodeToString(RangeDoubleSerializer, start..end) shouldEqualJson """ + { + "start": $start, + "end": $end + } + """.trimIndent() + } + } + + @Nested + inner class Deserialize { + + @Test + fun `should throw if start is missing`() { + val end = randomDouble(100.0, 200.0) + val json = """ + { + "end": $end + } + """.trimIndent() + assertThrows { + Json.decodeFromString(RangeDoubleSerializer, json) + } + } + + @Test + fun `should throw if end is missing`() { + val start = randomDouble(-100.0, 100.0) + val json = """ + { + "start": $start + } + """.trimIndent() + assertThrows { + Json.decodeFromString(RangeDoubleSerializer, json) + } + } + + @Test + fun `should deserialize using start and end`() { + val start = randomDouble(-100.0, 100.0) + val end = randomDouble(100.0, 200.0) + val json = """ + { + "start": $start, + "end": $end + } + """.trimIndent() + Json.decodeFromString(RangeDoubleSerializer, json) shouldBe (start..end) + } + + } +} diff --git a/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt b/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt index 76009c52..0274c3d4 100644 --- a/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt +++ b/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt @@ -5,9 +5,12 @@ import org.bukkit.World import java.util.* import kotlin.random.Random -val stringGenerator = generateSequence { UUID.randomUUID().toString() }.distinct().iterator() - -fun randomString() = stringGenerator.next() +fun randomString( + allowedChar: List = ('a'..'z') + ('A'..'Z') + ('0'..'9'), + size: Int = 50 +): String { + return List(size) { allowedChar.random() }.joinToString("") +} fun randomBoolean() = Random.nextBoolean() @@ -20,6 +23,10 @@ fun randomFloat(from: Float = Float.MIN_VALUE, until: Float = Float.MAX_VALUE) = fun randomDouble(from: Double = Double.MIN_VALUE, until: Double = Double.MAX_VALUE) = Random.nextDouble(from, until) +inline fun > randomEnum(): T { + return enumValues().random() +} + const val LIMIT_RANDOM_COORDINATE = 1000.0 fun randomLocation(world: World? = null): Location { diff --git a/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaTest.kt index 50e8e9e5..52355331 100644 --- a/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaTest.kt @@ -109,7 +109,6 @@ class SphereAreaTest { fun `should use positive for radius`() { val min = Location(worldMock, 0.0, 0.0, 0.0) val area = SphereArea(min, 5.0) - println(sqrt(5.0 * 5.0 / 3.0)) min.copy(x = 0.0, y = 0.0, z = 0.0) isIn area shouldBe true min.copy(x = 5.0, y = 0.0, z = 0.0) isIn area shouldBe true From f4c27ea68134781f2946611a89422fb4fec7fbc5 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 6 Aug 2023 14:25:46 +0200 Subject: [PATCH 121/143] test(cancellable): Add tests to cancel object --- .../api/extension/event/_Cancellable.kt | 9 ------- .../api/extension/CancellableExtTest.kt | 26 +++++++++++++++++++ 2 files changed, 26 insertions(+), 9 deletions(-) create mode 100644 src/test/kotlin/com/github/rushyverse/api/extension/CancellableExtTest.kt diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/event/_Cancellable.kt b/src/main/kotlin/com/github/rushyverse/api/extension/event/_Cancellable.kt index 9f593e97..0bf15aee 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/event/_Cancellable.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/event/_Cancellable.kt @@ -8,12 +8,3 @@ import org.bukkit.event.Cancellable public fun Cancellable.cancel() { isCancelled = true } - -/** - * Extension function allowing to cancel the current process by method calling with a condition. - */ -public inline fun T.cancelIf(condition: T.() -> Boolean) { - if (condition()) { - cancel() - } -} diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/CancellableExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/CancellableExtTest.kt new file mode 100644 index 00000000..3cd8856e --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/extension/CancellableExtTest.kt @@ -0,0 +1,26 @@ +package com.github.rushyverse.api.extension + +import com.github.rushyverse.api.extension.event.cancel +import io.kotest.matchers.shouldBe +import org.bukkit.event.Cancellable +import kotlin.test.Test + +class CancellableExtTest { + + @Test + fun `cancel() sets isCancelled to true`() { + var setCancel: Boolean? = null + val cancellable = object : Cancellable { + override fun isCancelled(): Boolean { + error("Should not be called") + } + + override fun setCancelled(cancel: Boolean) { + setCancel = cancel + } + + } + cancellable.cancel() + setCancel shouldBe true + } +} From c03486ec1bd69553ae11e4ec6ecd5d199cf54edf Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 6 Aug 2023 16:45:42 +0200 Subject: [PATCH 122/143] test(cancellable): Move class --- .../rushyverse/api/extension/{ => event}/CancellableExtTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/kotlin/com/github/rushyverse/api/extension/{ => event}/CancellableExtTest.kt (92%) diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/CancellableExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/event/CancellableExtTest.kt similarity index 92% rename from src/test/kotlin/com/github/rushyverse/api/extension/CancellableExtTest.kt rename to src/test/kotlin/com/github/rushyverse/api/extension/event/CancellableExtTest.kt index 3cd8856e..ba193e21 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/CancellableExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/event/CancellableExtTest.kt @@ -1,4 +1,4 @@ -package com.github.rushyverse.api.extension +package com.github.rushyverse.api.extension.event import com.github.rushyverse.api.extension.event.cancel import io.kotest.matchers.shouldBe From 25453a01e16fb7c0e04f3aa04d78afafc97e7583 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 6 Aug 2023 16:45:59 +0200 Subject: [PATCH 123/143] test(item-ext): Filter air from sequence --- .../com/github/rushyverse/api/extension/ItemStackExtTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/ItemStackExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/ItemStackExtTest.kt index afee343b..feb309c4 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/ItemStackExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/ItemStackExtTest.kt @@ -119,12 +119,12 @@ class ItemStackExtTest { val item1 = mockItem(Material.BEDROCK) val item2 = mockItem(Material.ACACIA_LEAVES) assertEquals( - listOf(item1, item2), listOf( + listOf(item1, item2), sequenceOf( item1, mockItem(Material.AIR), item2, mockItem(Material.AIR) - ).filterNotAir() + ).filterNotAir().toList() ) } } From 18101e5a92e01fe87903f057fe56f4a10e9fbe97 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 6 Aug 2023 16:46:19 +0200 Subject: [PATCH 124/143] test(number): Roman numerals --- .../rushyverse/api/extension/NumberExtTest.kt | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/test/kotlin/com/github/rushyverse/api/extension/NumberExtTest.kt diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/NumberExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/NumberExtTest.kt new file mode 100644 index 00000000..ff3bd935 --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/extension/NumberExtTest.kt @@ -0,0 +1,97 @@ +package com.github.rushyverse.api.extension + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import kotlin.test.Test + +class NumberExtTest { + + @Test + fun `roman numerals values associate all numbers to their roman numerals`() { + val expected = mapOf( + 1000 to "M", + 900 to "CM", + 500 to "D", + 400 to "CD", + 100 to "C", + 90 to "XC", + 50 to "L", + 40 to "XL", + 10 to "X", + 9 to "IX", + 5 to "V", + 4 to "IV", + 1 to "I" + ) + ROMAN_NUMERALS_VALUES shouldBe expected + } + + @Test + fun `roman numerals array should be ordered from the largest to the smallest`() { + val expected = listOf("M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I") + ROMAN_NUMERALS.toList() shouldBe expected + } + + @Test + fun `roman values array should be ordered from the largest to the smallest`() { + val expected = listOf(1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1) + ROMAN_VALUES.toList() shouldBe expected + } + + @Nested + inner class IntTest { + + @Nested + inner class RomanNumerals { + + @ParameterizedTest + @ValueSource(ints = [0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10]) + fun `should throw exception when number is negative or zero`(number: Int) { + val ex = assertThrows { number.toRomanNumerals() } + ex.message shouldBe "Number must be positive" + } + + @ParameterizedTest + @ValueSource(ints = [4000, 4001, 4002, 4003, 4004, 4005, 4006, 4007, 4008, 4009, 4010]) + fun `should throw exception when number is greater than 3999`(number: Int) { + val ex = assertThrows { number.toRomanNumerals() } + ex.message shouldBe "Number must be less than 4000" + } + + @ParameterizedTest + @ValueSource(ints = [1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000]) + fun `should return pure roman numerals`(number: Int) { + val expected = when (number) { + 1 -> "I" + 4 -> "IV" + 5 -> "V" + 9 -> "IX" + 10 -> "X" + 40 -> "XL" + 50 -> "L" + 90 -> "XC" + 100 -> "C" + 400 -> "CD" + 500 -> "D" + 900 -> "CM" + 1000 -> "M" + else -> throw IllegalArgumentException("Invalid number") + } + number.toRomanNumerals() shouldBe expected + } + + @Test + fun `should return complex roman numerals`() { + NumberExtTest::class.java.getResourceAsStream("/cases/roman/numerals.txt")!!.bufferedReader().useLines { lines -> + lines.forEach { line -> + val (number, expected) = line.split(" ") + number.toInt().toRomanNumerals() shouldBe expected + } + } + } + } + } +} From 391d92da729f278da9e3cdadefb0cadac7eafb49 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 6 Aug 2023 16:47:32 +0200 Subject: [PATCH 125/143] test: Remove unused files --- src/test/resources/map_image.png | Bin 206 -> 0 bytes src/test/resources/server.conf | 13 ------------- src/test/resources/world/DIM-1/data/raids.dat | Bin 89 -> 0 bytes src/test/resources/world/DIM1/data/raids_end.dat | Bin 89 -> 0 bytes src/test/resources/world/data/raids.dat | Bin 89 -> 0 bytes src/test/resources/world/entities/r.-1.-1.mca | 0 src/test/resources/world/entities/r.-1.0.mca | 0 src/test/resources/world/entities/r.0.-1.mca | 0 src/test/resources/world/entities/r.0.0.mca | 0 src/test/resources/world/level.dat | Bin 2008 -> 0 bytes src/test/resources/world/level.dat_old | Bin 2009 -> 0 bytes src/test/resources/world/region/r.-1.-1.mca | 0 src/test/resources/world/region/r.-1.0.mca | 0 src/test/resources/world/region/r.0.-1.mca | 0 src/test/resources/world/region/r.0.0.mca | 0 src/test/resources/world/session.lock | 1 - 16 files changed, 14 deletions(-) delete mode 100644 src/test/resources/map_image.png delete mode 100644 src/test/resources/server.conf delete mode 100644 src/test/resources/world/DIM-1/data/raids.dat delete mode 100644 src/test/resources/world/DIM1/data/raids_end.dat delete mode 100644 src/test/resources/world/data/raids.dat delete mode 100644 src/test/resources/world/entities/r.-1.-1.mca delete mode 100644 src/test/resources/world/entities/r.-1.0.mca delete mode 100644 src/test/resources/world/entities/r.0.-1.mca delete mode 100644 src/test/resources/world/entities/r.0.0.mca delete mode 100644 src/test/resources/world/level.dat delete mode 100644 src/test/resources/world/level.dat_old delete mode 100644 src/test/resources/world/region/r.-1.-1.mca delete mode 100644 src/test/resources/world/region/r.-1.0.mca delete mode 100644 src/test/resources/world/region/r.0.-1.mca delete mode 100644 src/test/resources/world/region/r.0.0.mca delete mode 100644 src/test/resources/world/session.lock diff --git a/src/test/resources/map_image.png b/src/test/resources/map_image.png deleted file mode 100644 index c0a4aae43bd8481dc16ac510cb639d71c7f5731a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 206 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58JL)Xl%kBoR}il_$lZzY=1HA;AcwQSBeIx* zfm;}a85w5HkpK$v1o(uwhPW*g^|cn)ls zc|=9c(W&0!f0|lU;9sBuT;LviS&0%ZNuc~6@vd$@?2>=$@ BDjcU4UW=gK^YE@O+J1fM26*nXfoPrRFxN(958~`CL;)ImF@(*xAio_KTDEz9r zduE(?p{2H`tKNI{K7a33i>O7l9ntTDlskEiABOg%|`ZX3dUTz16mXmd`h>dX=aBtXHuF}9|{HM zY|i;p3CEd$8j?-9hHQAm(K#b%ZBEC8u2N&d#du=9*8i zeX(eFNx14gGB7IBA#YM6g`>EAm9kW0!L%5ODF%rWhR?LzSm3*|ajl-vf9vWxPohs! zejym1>LtzdV_p^a|5dw&$K~xxPqVQ~_Fcp)wEYfp)buUVvLCX~n``74HV#YK2yF9lnKZ;ZdbR zo5z}82$dN*E!O7MIRgf!$K1Am{PVBm^|$;TMaMZBm0N3(dZ>mkyI>3U?j=^(X;-s2b~{V2f4NDkh3+Rz z0CY-i*WpP3A*!xB4dqOsjYuKF(k1zo^)2MdWzR7@b<*neHafDqvAxcnIEte$2<}!trKpX(KC5F ziGy+`Pj~?l$m^0X@J>2XYUa@vxa3v;ubo4Y!^EYU($kq-I5_v@6|1%)btGb8dDM02 z;2c~sti)<~fC3sV#ACztR293F?598rs=NV0Z@>wQL}qB|-d&&43$?JozdF*KfADgO zN;Pgr2yR(a<;G2u^3XbvGzUe$>`t=ed`7TifMf;NKGYeqd8t5$V}{Nouj+u}Yvifd zqI$=T6zHTekClfCs?x^WnYJAd2*$EFB@bCGA6~bp9h$Y;)@m`#EYI6%gK8aS`2F8M zdGgdlV~#POnZvFRrU>uEMV$>y?mt>Zs50rSLq5BSXde;Xms6*4mDZ1ywoG;vx+{u} zJhpDOheqktWkx$jdDYVF?Ogu_?f~8$yt|1L*&8iNE0>1X5;+Rbk z=i=uA0%7JKpPmt1d;ZN=)SR>4E_J(x6X_9n7|ELmW~j$$pXX{-Y58$_st`i*?Xvdn zyN5m3K!5wIuLpnse(xtA{$c&s->$#*JA^t1lEqmTod|Pd(glVK;4VhEhss60;U#~< zQ>Vt;oJl)rKXo5FBc-Cg#}vnU#4osf@YE??Z3|ubB4Yk1InYQWH%A1$at6J28pAkn z0%M#Y*W#BtfBV;e+Q0l#=FRk?^K96?^c`*Bkzs)L62f&=TD#2W=YcFqt8~)>Ow7Ot zq|(5-e^3x%_owhmSfu{I(^1Y;?}tkJ%nPV`tc-WagU{v;_~__|AKuve<3GypC91hT z`r>(=s*ls-oU66xn)adPaVc}fU1f?v!VL;l3Y_aot9A=l?!7-@Xy~OY^8=a=G|KRA z_PGRZ@yJQLFJ7Fv{|@*6XH-DG=2UaA zTtJkU(eTjsUC8+}E=dmSyoyV|_$REjKvH-hU-`moCAGVoHIWk&1kn2!bv3`ELD6#_ z%|yHVQxgWGsWM%(-g1%x(iBJdrUC?Y9O#{r7lm$F{Cq$J~(*?&J&sZS-;JiZEM6#kfTtkpmwt56d;cwrx;rK2(;Y qC90yA&m&P;g#QIJ?9X={4*&p*H~ZKC diff --git a/src/test/resources/world/level.dat_old b/src/test/resources/world/level.dat_old deleted file mode 100644 index 822cb27b024e449e2059669dcd8f7e079795183e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2009 zcmV;~2PXI*iwFP!00000|AkjgY$Hh(evW_QcI^C*m>Dko?F~jVyX+!BXvRNDHjvo~ zah41#A*s_8$uHT{jDN&;*Mk5iX(#}>Yw4G7)#e4jS>*=Y8`GRy+Gm+o~s!(OIQ)-o|e!yaW zI-9zsQb5f<*B4xOG;}icjQ4^iLQ3|6B99B{HQLSAdn{&SehhA@!t?uoe&U{=)MzK$ zK4aRc;5W^K-Hf?Gn`cVP=zu4EZY}mriPnZHNzEXfaATduhV$s(f4cwCuXOai5^arn z!ZoveFyhRnnj1)IM6fC`LM5g_JF!T3sM*MVMZuU$cR+)JoKNWn)y@2{`dmt#szagR zoXrLQRKjs4pqgYet|1#9adgfIYAxuP&}FJkxEN2Y_gX+Z#_BY5q6bb3Eh6qcxuEMt$-awtiMHQDj+(wjTJ}ShxI{-ap|~u+WHVC+ zi_7>H-U_*B60P~#A=>uhvS$l#74Ht-b-bH+1D{Vst3G##ns_(xmP^#kbod6cg-4|l zZ60fWAyjJQEMHqx=NuTA9&_9N;ZMJm)!*v3kk_aiR6;*tQ!`O6scOIhfxN54$p12} z9Kz`4UAW{ezy;Or-MRPWJFmi&)gGHI0}6^4Kw|}yIL@gQoH6rIMUnq#F)7t$SH-b| zmr|K-U59-=j$$MF&IbU8ZfdNGJIa{73&4&m^}r$4Nh)16ZAJ>P5&=9MqX#$?9m3s+ z)V|RCRG>`rr5iDOH$NKaDv~3fjO~P~Z@m8cYj1#K1o{lZ3XV<9qcr4Ej}O!FVKP!w z9WiOJb2}1-4JF@`GppG{R2~>aO{bHS(x$Y-S z0CWm%*WpP3A*!xB3*}s)jYuKF(k1zo^)2MdWzR7@%(}M7!@ai&HaF-IOdT+X`OK6jGoKe zNgR|jdBSsuKwcMofp^l8Qge?sz$LHxzjY2p4ilGZO3&tU;o!`ZSFGBK)RBmV72_My&@ElLGC95Zw#c~u7#UoA_$ z230$5q(CQ)d8|BCP?tMEi*7zMMIY%d~#1v}LlZ&|Oh% ziCrr9Xt+csk0Ms{K%DpLqe5kCpKbdGP7X0UsQF|NR?#zyC+^y+Re& zN1uOGrSjwHamLlyb4~lu^0<(>=B_gNAmIjuG6l|cg;l$`EBD@=Ff{bSmDvGx2O35A zH~U-yw|L~F_ocGgiNOMsGbe2w!CIdRNbzb_xXFl<_zt(8$%5}^g`QT2*5YR>+m#zr2Rg{lg4=aaU~yJy;Y(rdf7Y@6-D@818S1*9S;BifKc&j diff --git a/src/test/resources/world/region/r.-1.-1.mca b/src/test/resources/world/region/r.-1.-1.mca deleted file mode 100644 index e69de29b..00000000 diff --git a/src/test/resources/world/region/r.-1.0.mca b/src/test/resources/world/region/r.-1.0.mca deleted file mode 100644 index e69de29b..00000000 diff --git a/src/test/resources/world/region/r.0.-1.mca b/src/test/resources/world/region/r.0.-1.mca deleted file mode 100644 index e69de29b..00000000 diff --git a/src/test/resources/world/region/r.0.0.mca b/src/test/resources/world/region/r.0.0.mca deleted file mode 100644 index e69de29b..00000000 diff --git a/src/test/resources/world/session.lock b/src/test/resources/world/session.lock deleted file mode 100644 index 0d7e5f85..00000000 --- a/src/test/resources/world/session.lock +++ /dev/null @@ -1 +0,0 @@ -☃ \ No newline at end of file From 0566278c4ccb41fa989f75769bd445c0f9eca676 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 6 Aug 2023 16:55:16 +0200 Subject: [PATCH 126/143] test(player-profile): Add tests for texture property --- .../api/extension/PlayerProfileExtTest.kt | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/test/kotlin/com/github/rushyverse/api/extension/PlayerProfileExtTest.kt diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/PlayerProfileExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/PlayerProfileExtTest.kt new file mode 100644 index 00000000..fc4fe957 --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/extension/PlayerProfileExtTest.kt @@ -0,0 +1,49 @@ +package com.github.rushyverse.api.extension + +import be.seeseemelk.mockbukkit.MockBukkit +import com.github.rushyverse.api.utils.randomString +import io.kotest.matchers.shouldBe +import org.bukkit.Bukkit +import java.util.* +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test + +class PlayerProfileExtTest { + + @BeforeTest + fun onBefore() { + MockBukkit.mock() + } + + @AfterTest + fun onAfter() { + MockBukkit.unmock() + } + + @Test + fun `should define textures property`() { + val profile = Bukkit.createProfile(UUID.randomUUID()) + val skin = randomString() + val signature = randomString() + profile.setTextures(skin, signature) + + profile.properties.find { it.name == "textures" }!!.let { + it.value shouldBe skin + it.signature shouldBe signature + } + } + + @Test + fun `should get textures property`() { + val profile = Bukkit.createProfile(UUID.randomUUID()) + val skin = randomString() + val signature = randomString() + profile.setTextures(skin, signature) + + profile.getTexturesProperty()!!.let { + it.value shouldBe skin + it.signature shouldBe signature + } + } +} From 036a0e9978b7acf17108b716648c5711da1983fe Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 6 Aug 2023 17:27:56 +0200 Subject: [PATCH 127/143] feat(material): Add extension to check is wool --- .../rushyverse/api/extension/_Material.kt | 20 +++++ .../api/extension/MaterialExtTest.kt | 88 +++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 src/main/kotlin/com/github/rushyverse/api/extension/_Material.kt create mode 100644 src/test/kotlin/com/github/rushyverse/api/extension/MaterialExtTest.kt diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Material.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Material.kt new file mode 100644 index 00000000..492cc27a --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Material.kt @@ -0,0 +1,20 @@ +package com.github.rushyverse.api.extension + +import org.bukkit.Material +import org.bukkit.Tag + +/** + * Checks whether the material is wool. + * + * @receiver Material the material to be checked + * @return `true` if the material is wool, `false` otherwise + */ +public fun Material.isWool(): Boolean = Tag.WOOL.isTagged(this) + +/** + * Checks if the material is a wool carpet. + * + * @receiver The material to check. + * @return `true` if the material is a wool carpet, `false` otherwise. + */ +public fun Material.isWoolCarpet(): Boolean = Tag.WOOL_CARPETS.isTagged(this) diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/MaterialExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/MaterialExtTest.kt new file mode 100644 index 00000000..f85b8959 --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/extension/MaterialExtTest.kt @@ -0,0 +1,88 @@ +package com.github.rushyverse.api.extension + +import be.seeseemelk.mockbukkit.MockBukkit +import io.kotest.matchers.shouldBe +import org.bukkit.Material +import org.junit.jupiter.api.Nested +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test + +class MaterialExtTest { + + @BeforeTest + fun onBefore() { + MockBukkit.mock() + } + + @AfterTest + fun onAfter() { + MockBukkit.unmock() + } + + @Nested + inner class IsWool { + + @Test + fun `should be wool`() { + Material.BLACK_WOOL.isWool() shouldBe true + Material.BLUE_WOOL.isWool() shouldBe true + Material.BROWN_WOOL.isWool() shouldBe true + Material.CYAN_WOOL.isWool() shouldBe true + Material.GRAY_WOOL.isWool() shouldBe true + Material.GREEN_WOOL.isWool() shouldBe true + Material.LIGHT_BLUE_WOOL.isWool() shouldBe true + Material.LIGHT_GRAY_WOOL.isWool() shouldBe true + Material.LIME_WOOL.isWool() shouldBe true + Material.MAGENTA_WOOL.isWool() shouldBe true + Material.ORANGE_WOOL.isWool() shouldBe true + Material.PINK_WOOL.isWool() shouldBe true + Material.PURPLE_WOOL.isWool() shouldBe true + Material.RED_WOOL.isWool() shouldBe true + Material.WHITE_WOOL.isWool() shouldBe true + Material.YELLOW_WOOL.isWool() shouldBe true + } + + @Test + fun `should not be wool`() { + Material.AIR.isWool() shouldBe false + Material.BLACK_CARPET.isWool() shouldBe false + Material.BLUE_CARPET.isWool() shouldBe false + Material.ACACIA_FENCE.isWool() shouldBe false + } + + } + + @Nested + inner class IsWoolCarpet { + + @Test + fun `should be wool carpet`() { + Material.BLACK_CARPET.isWoolCarpet() shouldBe true + Material.BLUE_CARPET.isWoolCarpet() shouldBe true + Material.BROWN_CARPET.isWoolCarpet() shouldBe true + Material.CYAN_CARPET.isWoolCarpet() shouldBe true + Material.GRAY_CARPET.isWoolCarpet() shouldBe true + Material.GREEN_CARPET.isWoolCarpet() shouldBe true + Material.LIGHT_BLUE_CARPET.isWoolCarpet() shouldBe true + Material.LIGHT_GRAY_CARPET.isWoolCarpet() shouldBe true + Material.LIME_CARPET.isWoolCarpet() shouldBe true + Material.MAGENTA_CARPET.isWoolCarpet() shouldBe true + Material.ORANGE_CARPET.isWoolCarpet() shouldBe true + Material.PINK_CARPET.isWoolCarpet() shouldBe true + Material.PURPLE_CARPET.isWoolCarpet() shouldBe true + Material.RED_CARPET.isWoolCarpet() shouldBe true + Material.WHITE_CARPET.isWoolCarpet() shouldBe true + Material.YELLOW_CARPET.isWoolCarpet() shouldBe true + } + + @Test + fun `should not be wool carpet`() { + Material.AIR.isWoolCarpet() shouldBe false + Material.BLACK_WOOL.isWoolCarpet() shouldBe false + Material.BLUE_WOOL.isWoolCarpet() shouldBe false + Material.ACACIA_FENCE.isWoolCarpet() shouldBe false + } + + } +} From 47a629d1c115ab9affce4e128123ddf2b1e68341 Mon Sep 17 00:00:00 2001 From: Quentin Date: Sun, 6 Aug 2023 23:19:00 +0200 Subject: [PATCH 128/143] fix(detekt): Resolve detekt issues (#67) --- build.gradle.kts | 1 + config/detekt/detekt.yml | 81 +++++++++---------- .../com/github/rushyverse/api/APIPlugin.kt | 8 ++ .../com/github/rushyverse/api/Plugin.kt | 47 ++++++++--- .../api/configuration/reader/FileReader.kt | 11 ++- .../configuration/reader/YamlFileReader.kt | 4 +- .../exception/SilentCancellationException.kt | 2 +- .../api/extension/_CommandSender.kt | 12 +-- .../rushyverse/api/extension/_Component.kt | 9 ++- .../api/extension/_CoroutineScope.kt | 4 +- .../rushyverse/api/extension/_Duration.kt | 2 +- .../api/extension/_PlayerProfile.kt | 2 +- .../rushyverse/api/extension/_String.kt | 50 +++++++----- .../rushyverse/api/extension/_Villager.kt | 2 +- .../github/rushyverse/api/extension/_World.kt | 2 +- .../api/extension/event/_Cancellable.kt | 9 +++ .../api/extension/event/_Listener.kt | 10 ++- .../github/rushyverse/api/game/GameData.kt | 10 ++- .../github/rushyverse/api/game/GameState.kt | 29 ++++++- .../rushyverse/api/game/SharedGameData.kt | 53 ++++++++++-- .../api/game/stats/KillableStats.kt | 14 +++- .../github/rushyverse/api/game/stats/Stats.kt | 11 +++ .../api/game/stats/WinnableStats.kt | 12 ++- .../rushyverse/api/game/team/TeamType.kt | 48 ++++------- .../rushyverse/api/item/CraftBuilder.kt | 19 ++++- .../exception/CraftResultMissingException.kt | 2 +- .../rushyverse/api/koin/CraftContext.kt | 34 +++++--- .../github/rushyverse/api/player/Client.kt | 31 ++++++- .../api/player/ClientManagerImpl.kt | 5 -- .../exception/ClientNotFoundException.kt | 2 +- .../exception/PlayerNotFoundException.kt | 2 +- .../player/scoreboard/ScoreboardManager.kt | 26 ++++++ .../rushyverse/api/schedule/Scheduler.kt | 2 +- .../rushyverse/api/schedule/SchedulerTask.kt | 6 +- .../api/serializer/EnumSerializer.kt | 4 +- .../api/serializer/ItemStackSerializer.kt | 20 ++--- .../ResourceBundleTranslationProvider.kt | 4 +- .../api/translation/SupportedLanguage.kt | 39 ++++++++- .../api/translation/TranslationProvider.kt | 6 +- .../rushyverse/api/world/CylinderArea.kt | 35 +++++++- .../exception/WorldDifferentException.kt | 13 ++- .../world/exception/WorldNotFoundException.kt | 2 +- .../github/rushyverse/api/AbstractKoinTest.kt | 2 +- .../api/delegate/DelegateWorldTest.kt | 4 +- .../api/extension/CoroutineScopeExt.kt | 2 +- .../api/extension/DurationExtTest.kt | 2 +- .../api/extension/RunnableExtTest.kt | 2 +- .../rushyverse/api/extension/StringExtTest.kt | 53 ++++++------ .../serializer/EnchantmentSerializerTest.kt | 8 +- .../api/serializer/LocationSerializerTest.kt | 4 +- .../serializer/RangeDoubleSerializerTest.kt | 2 +- .../github/rushyverse/api/utils/Generator.kt | 1 - .../rushyverse/api/utils/LocationTestUtils.kt | 2 +- .../api/world/cube/CubeAreaSerializerTest.kt | 4 +- .../cylinder/CylinderAreaSerializerTest.kt | 4 +- .../world/sphere/SphereAreaSerializerTest.kt | 4 +- 56 files changed, 533 insertions(+), 246 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 874edb67..d9de892c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -107,6 +107,7 @@ kotlin { languageSettings { optIn("kotlin.RequiresOptIn") optIn("kotlin.ExperimentalStdlibApi") + optIn("kotlinx.serialization.ExperimentalSerializationApi") optIn("kotlin.contracts.ExperimentalContracts") optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") } diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index e25786bc..9c6a77e8 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -2,10 +2,10 @@ build: maxIssues: 0 excludeCorrectable: false weights: - # complexity: 2 - # LongParameterList: 1 - # style: 1 - # comments: 1 + # complexity: 2 + # LongParameterList: 1 + # style: 1 + # comments: 1 config: validation: true @@ -34,16 +34,16 @@ processors: console-reports: active: true exclude: - - 'ProjectStatisticsReport' - - 'ComplexityReport' - - 'NotificationReport' - - 'FindingsReport' - - 'FileBasedFindingsReport' + - 'ProjectStatisticsReport' + - 'ComplexityReport' + - 'NotificationReport' + - 'FindingsReport' + - 'FileBasedFindingsReport' # - 'LiteFindingsReport' output-reports: active: true -# exclude: + # exclude: # - 'TxtOutputReport' # - 'XmlOutputReport' # - 'HtmlOutputReport' @@ -67,17 +67,15 @@ comments: endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' KDocReferencesNonPublicProperty: active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] OutdatedDocumentation: active: false matchTypeParameters: true matchDeclarationsOrder: true allowParamOnConstructorProperties: false - excludes: - - '**/*Application*' UndocumentedPublicClass: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] searchInNestedClass: true searchInInnerClass: true searchInInnerObject: true @@ -85,11 +83,11 @@ comments: searchInProtectedClass: false UndocumentedPublicFunction: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] searchProtectedFunction: false UndocumentedPublicProperty: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] searchProtectedProperty: false complexity: @@ -122,9 +120,10 @@ complexity: - 'run' - 'use' - 'with' + excludes: [ '**/serializer/**', '**/test/**' ] LabeledExpression: active: false - ignoredLabels: [] + ignoredLabels: [ ] LargeClass: active: true threshold: 600 @@ -137,7 +136,7 @@ complexity: constructorThreshold: 7 ignoreDefaultParameters: false ignoreDataClasses: true - ignoreAnnotatedParameter: [] + ignoreAnnotatedParameter: [ ] MethodOverloading: active: false threshold: 6 @@ -161,14 +160,14 @@ complexity: active: true StringLiteralDuplication: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] threshold: 3 ignoreAnnotation: true excludeStringsWithLessThan5Characters: true ignoreStringsRegex: '$^' TooManyFunctions: active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] thresholdInFiles: 11 thresholdInClasses: 11 thresholdInInterfaces: 11 @@ -188,6 +187,7 @@ coroutines: - 'IO' - 'Default' - 'Unconfined' + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] RedundantSuspendModifier: active: true SleepInsteadOfDelay: @@ -245,7 +245,7 @@ exceptions: - 'toString' InstanceOfCheckForException: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] NotImplementedDeclaration: active: false ObjectExtendsThrowable: @@ -271,7 +271,7 @@ exceptions: active: false ThrowingExceptionsWithoutMessageOrCause: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] exceptions: - 'ArrayIndexOutOfBoundsException' - 'Exception' @@ -286,7 +286,7 @@ exceptions: active: true TooGenericExceptionCaught: active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] exceptionNames: - 'ArrayIndexOutOfBoundsException' - 'Error' @@ -323,7 +323,7 @@ naming: enumEntryPattern: '[A-Z][_a-zA-Z0-9]*' ForbiddenClassName: active: false - forbiddenName: [] + forbiddenName: [ ] FunctionMaxLength: active: false maximumFunctionNameLength: 30 @@ -332,7 +332,7 @@ naming: minimumFunctionNameLength: 3 FunctionNaming: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] functionPattern: '[a-z][a-zA-Z0-9]*' excludeClassPattern: '$^' FunctionParameterNaming: @@ -390,10 +390,10 @@ performance: threshold: 3 ForEachOnRange: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] SpreadOperator: active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] UnnecessaryPartOfBinaryExpression: active: true UnnecessaryTemporaryInstantiation: @@ -426,7 +426,7 @@ potential-bugs: - 'java.util.HashMap' ElseCaseInsteadOfExhaustiveWhen: active: true - ignoredSubjectTypes: [] + ignoredSubjectTypes: [ ] EqualsAlwaysReturnsTrueOrFalse: active: true EqualsWithHashCodeExist: @@ -452,7 +452,7 @@ potential-bugs: - 'kotlin.sequences.Sequence' - 'kotlinx.coroutines.flow.*Flow' - 'java.util.stream.*Stream' - ignoreFunctionCall: [] + ignoreFunctionCall: [ ] ImplicitDefaultLocale: active: true ImplicitUnitReturnType: @@ -466,13 +466,13 @@ potential-bugs: active: true LateinitUsage: active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] ignoreOnClassesPattern: '' MapGetWithNotNullAssertionOperator: active: true MissingPackageDeclaration: active: false - excludes: ['**/*.kts'] + excludes: [ '**/*.kts' ] NullCheckOnMutableProperty: active: false NullableToStringCall: @@ -491,7 +491,7 @@ potential-bugs: active: true UnsafeCallOnNullableType: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] UnsafeCast: active: true UnusedUnaryOperator: @@ -572,15 +572,14 @@ style: value: 'java.lang.annotation.Inherited' ForbiddenComment: active: true - values: + comments: - 'FIXME:' - 'STOPSHIP:' - 'TODO:' allowedPatterns: '' - customMessage: '' ForbiddenImport: active: false - imports: [] + imports: [ ] forbiddenPatterns: '' ForbiddenMethodCall: active: false @@ -591,7 +590,7 @@ style: value: 'kotlin.io.println' ForbiddenSuppress: active: false - rules: [] + rules: [ ] ForbiddenVoid: active: true ignoreOverridden: false @@ -600,15 +599,13 @@ style: active: true ignoreOverridableFunction: true ignoreActualFunction: true - excludedFunctions: [] + excludedFunctions: [ ] LoopWithTooManyJumpStatements: active: true maxJumpCount: 1 MagicNumber: active: true - ignoreAnnotated: - - org.springframework.core.annotation.Order - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts'] + excludes: [ '**/_Number.kt', '**/serializer/**', '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts' ] ignoreNumbers: - '-1' - '0' @@ -659,8 +656,6 @@ style: active: true OptionalUnit: active: true - OptionalWhenBraces: - active: true PreferToOverPairSyntax: active: true ProtectedMemberInFinalClass: @@ -688,7 +683,7 @@ style: StringShouldBeRawString: active: true maxEscapedCharacterCount: 2 - ignoredCharacters: [] + ignoredCharacters: [ ] ThrowsCount: active: true max: 2 diff --git a/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt b/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt index 1d49d241..52bd1473 100644 --- a/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt +++ b/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt @@ -13,7 +13,15 @@ import org.bukkit.plugin.java.JavaPlugin public class APIPlugin : JavaPlugin() { public companion object { + /** + * A unique identifier for this plugin. This ID is used for tasks like identifying + * the Koin application, loading Koin modules, etc. + */ public const val ID_API: String = "api" + + /** + * This ID is used to identify the resource bundle of API translations in the project files. + */ public const val BUNDLE_API: String = "api_translate" } diff --git a/src/main/kotlin/com/github/rushyverse/api/Plugin.kt b/src/main/kotlin/com/github/rushyverse/api/Plugin.kt index c878250a..e9c44cfa 100644 --- a/src/main/kotlin/com/github/rushyverse/api/Plugin.kt +++ b/src/main/kotlin/com/github/rushyverse/api/Plugin.kt @@ -15,7 +15,6 @@ import com.github.rushyverse.api.player.ClientManager import com.github.rushyverse.api.player.ClientManagerImpl import com.github.rushyverse.api.serializer.* import com.github.rushyverse.api.translation.ResourceBundleTranslationProvider -import com.github.rushyverse.api.translation.SupportedLanguage import com.github.rushyverse.api.translation.registerResourceBundleForSupportedLocales import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin import kotlinx.serialization.modules.SerializersModule @@ -27,10 +26,17 @@ import org.koin.dsl.bind import java.util.* /** - * Abstract plugin with the necessary component to create a plugin. + * Represents the base functionality required to create a plugin. + * This abstract class provides necessary tools and life-cycle methods to facilitate the creation + * and management of a plugin that utilizes asynchronous operations, dependency injection, and + * other utility functions. */ public abstract class Plugin : SuspendingJavaPlugin() { + /** + * A unique identifier for this plugin. This ID is used for tasks like identifying + * the Koin application, loading Koin modules, etc. + */ public abstract val id: String override suspend fun onEnableAsync() { @@ -44,15 +50,31 @@ public abstract class Plugin : SuspendingJavaPlugin() { registerListener { VillagerListener(this) } } + /** + * Creates and loads a Koin module that stores instances of this plugin and of his child. + * + * @return The Koin module containing the plugin instances. + */ protected inline fun modulePlugin(): Module = loadModule(id) { single { this@Plugin } single { this@Plugin as T } } + /** + * Creates and loads a Koin module containing client management components. + * + * @return The Koin module for client management. + */ protected fun moduleClients(): Module = loadModule(id) { single { ClientManagerImpl() } bind ClientManager::class } + /** + * Creates and loads a Koin module with Bukkit-specific components. + * Can be overridden by derived classes to provide additional or customized components. + * + * @return The Koin module for Bukkit components. + */ protected open fun moduleBukkit(): Module = loadModule(id) { single { getLogger() } } @@ -63,8 +85,12 @@ public abstract class Plugin : SuspendingJavaPlugin() { } /** - * Create a new instance of yaml reader. - * @return The instance of the yaml reader. + * Creates a new YAML reader instance to handle YAML configurations. + * Allows for customization of serializers and configurations. + * + * @param configuration Configuration options for the YAML reader. + * @param serializerModuleBuilder Provides additional serializers. + * @return A configured YAML reader instance. */ protected open fun createYamlReader( configuration: YamlConfiguration = YamlConfiguration(), @@ -90,15 +116,18 @@ public abstract class Plugin : SuspendingJavaPlugin() { } /** - * Create a new instance of a client. - * @param player Player linked to the client. - * @return C The instance of the client. + * Abstract function to create a new client instance associated with a given player. + * + * @param player The player for whom the client instance should be created. + * @return The created client instance. */ public abstract fun createClient(player: Player): Client /** - * Create a translation provider to provide translations for the [supported languages][SupportedLanguage]. - * @return New translation provider. + * Creates a new translation provider to fetch translations for the supported languages. + * Can be overridden by derived classes to provide custom translation providers. + * + * @return A translation provider configured for the supported languages. */ protected open suspend fun createTranslationProvider(): ResourceBundleTranslationProvider = ResourceBundleTranslationProvider().apply { diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/reader/FileReader.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/reader/FileReader.kt index be857938..1c0ac6f1 100644 --- a/src/main/kotlin/com/github/rushyverse/api/configuration/reader/FileReader.kt +++ b/src/main/kotlin/com/github/rushyverse/api/configuration/reader/FileReader.kt @@ -1,5 +1,6 @@ package com.github.rushyverse.api.configuration.reader +import com.github.rushyverse.api.serializer.LocationSerializer import kotlinx.serialization.KSerializer import kotlinx.serialization.StringFormat import kotlinx.serialization.serializer @@ -19,13 +20,18 @@ public inline fun IFileReader.readConfigurationFile(configFile: Stri */ public interface IFileReader { + /** + * Format to read string from configuration file. + * The field should contain custom serializer for Bukkit classes like [LocationSerializer]. + */ public val format: StringFormat /** * Load the configuration from the given file. * @param clazz Type of configuration class to load. * @param filename Configuration file to load based on the "plugins/${plugin.name}" directory. - * So if the plugin name is "MyPlugin" and the config file is "config.yml", the file will be loaded from "plugins/MyPlugin/config.yml". + * So if the plugin name is "MyPlugin" and the config file is "config.yml", + * the file will be loaded from "plugins/MyPlugin/config.yml". * @return The configuration loaded from the given file. */ public fun readConfigurationFile(clazz: KClass, filename: String): T @@ -34,7 +40,8 @@ public interface IFileReader { * Load the configuration from the given file. * @param serializer Serializer to deserialize the configuration to the given type. * @param filename Configuration file to load based on the "plugins/${plugin.name}" directory. - * So if the plugin name is "MyPlugin" and the config file is "config.yml", the file will be loaded from "plugins/MyPlugin/config.yml". + * So if the plugin name is "MyPlugin" and the config file is "config.yml", + * the file will be loaded from "plugins/MyPlugin/config.yml". * @return The configuration loaded from the given file. */ public fun readConfigurationFile(serializer: KSerializer, filename: String): T diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReader.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReader.kt index 36dad032..57a7805b 100644 --- a/src/main/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReader.kt +++ b/src/main/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReader.kt @@ -1,6 +1,5 @@ package com.github.rushyverse.api.configuration.reader -import com.charleskorn.kaml.Yaml import kotlinx.serialization.KSerializer import kotlinx.serialization.StringFormat import kotlinx.serialization.serializer @@ -15,7 +14,6 @@ import kotlin.reflect.full.createType * Read configuration from YAML file. * If the file does not exist, it will be created from the resource with the same name. * @property plugin Plugin to get the data folder from. - * @property format [Yaml] configuration to use. */ public class YamlFileReader( public val plugin: JavaPlugin, @@ -68,7 +66,7 @@ public class YamlFileReader( @Blocking private fun createFileFromResource(target: File, resourceFile: String) { val resource = plugin::class.java.getResourceAsStream("/$resourceFile") - ?: throw IllegalStateException("Cannot find resource $resourceFile in the plugin.") + ?: error("Cannot find resource $resourceFile in the plugin.") resource.bufferedReader().use { reader -> target.bufferedWriter().use { writer -> diff --git a/src/main/kotlin/com/github/rushyverse/api/coroutine/exception/SilentCancellationException.kt b/src/main/kotlin/com/github/rushyverse/api/coroutine/exception/SilentCancellationException.kt index 0875dc5f..c67dfb71 100644 --- a/src/main/kotlin/com/github/rushyverse/api/coroutine/exception/SilentCancellationException.kt +++ b/src/main/kotlin/com/github/rushyverse/api/coroutine/exception/SilentCancellationException.kt @@ -17,4 +17,4 @@ public class SilentCancellationException(message: String, cause: Throwable? = nu stackTrace = emptyArray() return this } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_CommandSender.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_CommandSender.kt index 8badf3a3..74d5a0cc 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_CommandSender.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_CommandSender.kt @@ -8,10 +8,12 @@ import org.bukkit.command.CommandSender * @receiver Sender that will receive the message. * @param message Message. */ -public fun CommandSender.sendMessageError(message: String): Unit = sendMessage(text { - content(message) - color(NamedTextColor.RED) -}) +public fun CommandSender.sendMessageError(message: String){ + sendMessage(text { + content(message) + color(NamedTextColor.RED) + }) +} /** * Verify if a sender has several permissions. @@ -33,4 +35,4 @@ public inline fun CommandSender.hasPermissions(permissions: Array): Bool */ @Suppress("NOTHING_TO_INLINE") public inline fun CommandSender.hasPermissions(permissions: Iterable): Boolean = - permissions.all(this::hasPermission) \ No newline at end of file + permissions.all(this::hasPermission) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Component.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Component.kt index 1af8ad3a..36e012b5 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_Component.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Component.kt @@ -152,7 +152,8 @@ public fun Component.withoutUnderlined(): Component = this.decoration(TextDecoration.UNDERLINED, TextDecoration.State.FALSE) /** - * Set as [not set][TextDecoration.State.NOT_SET] the [underlined][TextDecoration.UNDERLINED] decoration of the component. + * Set as [not set][TextDecoration.State.NOT_SET] the + * [underlined][TextDecoration.UNDERLINED] decoration of the component. * @receiver Component to transform. * @return The same component. */ @@ -176,7 +177,8 @@ public fun Component.withoutStrikethrough(): Component = this.decoration(TextDecoration.STRIKETHROUGH, TextDecoration.State.FALSE) /** - * Set as [not set][TextDecoration.State.NOT_SET] the [strikethrough][TextDecoration.STRIKETHROUGH] decoration of the component. + * Set as [not set][TextDecoration.State.NOT_SET] the + * [strikethrough][TextDecoration.STRIKETHROUGH] decoration of the component. * @receiver Component to transform. * @return The same component. */ @@ -199,7 +201,8 @@ public fun Component.withoutObfuscated(): Component = this.decoration(TextDecoration.OBFUSCATED, TextDecoration.State.FALSE) /** - * Set as [not set][TextDecoration.State.NOT_SET] the [obfuscated][TextDecoration.OBFUSCATED] decoration of the component. + * Set as [not set][TextDecoration.State.NOT_SET] the + * [obfuscated][TextDecoration.OBFUSCATED] decoration of the component. * @receiver Component to transform. * @return The same component. */ diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt index 30828517..487b5772 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt @@ -9,8 +9,8 @@ import kotlin.coroutines.coroutineContext import kotlin.time.Duration /** - * Calls the specified suspending block with a given coroutine context into the [CoroutineScope], suspends until it completes, and returns - * the result. + * Calls the specified suspending block with a given coroutine context into the [CoroutineScope], + * suspends until it completes, and returns the result. * * The resulting context for the [block] is derived by merging the current [coroutineContext] with the * specified context using `coroutineContext + context` (see [CoroutineContext.plus]). diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Duration.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Duration.kt index bb7956d7..948e92f6 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_Duration.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Duration.kt @@ -42,4 +42,4 @@ public val UShort.ticks: Duration get() = toShort().ticks * Get an instance of [Duration] corresponding to the time of ticks. * 1 tick corresponding to 50 milliseconds, 20 ticks to 1 second. */ -public val Short.ticks: Duration get() = (this * MILLISECOND_PER_TICK).milliseconds \ No newline at end of file +public val Short.ticks: Duration get() = (this * MILLISECOND_PER_TICK).milliseconds diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_PlayerProfile.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_PlayerProfile.kt index 8200ae62..eb49477f 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_PlayerProfile.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_PlayerProfile.kt @@ -26,4 +26,4 @@ public inline fun PlayerProfile.setTextures(skin: String, signature: String? = n */ @Suppress("NOTHING_TO_INLINE") public inline fun PlayerProfile.getTexturesProperty(): ProfileProperty? = - properties.find { it.name == PROPERTY_TEXTURES } \ No newline at end of file + properties.find { it.name == PROPERTY_TEXTURES } diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt index ec7a13b3..2c619408 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt @@ -9,7 +9,6 @@ import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.minimessage.MiniMessage import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver import net.kyori.adventure.text.minimessage.tag.standard.StandardTags -import org.bukkit.ChatColor import java.math.BigInteger import java.util.* @@ -27,12 +26,25 @@ private val MINI_MESSAGE_NON_STRICT: MiniMessage = MiniMessage.builder() public const val UUID_SIZE: Int = 36 /** - * Apply the coloration of Bukkit - * @see ChatColor.translateAlternateColorCodes - * @receiver String that will be analyzed to create a String colored - * @return A new String with the coloration of Bukkit + * Number of dashes in a UUID. */ -public fun String.colored(): String = ChatColor.translateAlternateColorCodes('&', this) +private const val NUMBER_DASHES_UUID = 4 + +/** + * Radix for hexadecimal numbers. + */ +private const val HEXADECIMAL_RADIX = 16 + +/** + * The constant value representing the number of bits in a UUID high and low bits. + */ +private const val UUID_HIGH_LOW_BITS: Int = 64 + +/** + * Default max line for a lore line. + * This value is defined by looking with the default Minecraft size application. + */ +public const val DEFAULT_LORE_LINE_LENGTH: Int = 30 /** * Wraps a given string with a color tag. @@ -122,19 +134,15 @@ public fun String.toUUID(): UUID { val length = this.length if (length == UUID_SIZE) { return toUUIDStrict() - } else if (length == UUID_SIZE - 4) { // -4 because of dashes - val idHex = BigInteger(this, 16) - return UUID(idHex.shiftRight(64).toLong(), idHex.toLong()) + } else { + if (length == UUID_SIZE - NUMBER_DASHES_UUID) { // -4 because of dashes + val idHex = BigInteger(this, HEXADECIMAL_RADIX) + return UUID(idHex.shiftRight(UUID_HIGH_LOW_BITS).toLong(), idHex.toLong()) + } } throw IllegalArgumentException("Invalid UUID format: $this") } -/** - * Default max line for a lore line. - * This value is defined by looking with the default Minecraft size application. - */ -public const val DEFAULT_LORE_LINE_LENGTH: Int = 30 - /** * Transform a sequence of strings to a component. * Each string will be transformed into a component and then joined together by a new line. @@ -168,7 +176,8 @@ public inline fun Collection.toLore( /** * Transform a string into a list of string by cutting it. - * If the string is too large and doesn't have any space, it will be cut each [lineLength] characters and a '-' will be added. + * If the string is too large and doesn't have any space, + * it will be cut each [lineLength] characters and a '-' will be added. * If the string contains a space, it will be cut at the space. * @receiver String to transform. * @param lineLength Max size of each string. @@ -180,7 +189,8 @@ public fun String.toFormattedLore(lineLength: Int = DEFAULT_LORE_LINE_LENGTH): L /** * Transform a string into a sequence of string by cutting. - * If the string is too large and doesn't have any space, it will be cut each [lineLength] characters and a '-' will be added. + * If the string is too large and doesn't have any space, + * it will be cut each [lineLength] characters and a '-' will be added. * If the string contains a space, it will be cut at the space. * @receiver String to transform. * @param lineLength Max size of each string. @@ -223,5 +233,7 @@ public fun String.toFormattedLoreSequence(lineLength: Int = DEFAULT_LORE_LINE_LE * @param tagResolver The tag resolver used to resolve the custom tags. * @return The component created from the string. */ -public fun String.asComponent(vararg tagResolver: TagResolver, miniMessage: MiniMessage = MINI_MESSAGE_NON_STRICT): Component = - miniMessage.deserialize(this, *tagResolver) +public fun String.asComponent( + vararg tagResolver: TagResolver, + miniMessage: MiniMessage = MINI_MESSAGE_NON_STRICT +): Component = miniMessage.deserialize(this, *tagResolver) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Villager.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Villager.kt index ade89841..e508bade 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_Villager.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Villager.kt @@ -44,4 +44,4 @@ public fun Villager.keepProfession(plugin: Plugin, keep: Boolean) { remove(key) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_World.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_World.kt index 14579085..968afd84 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_World.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_World.kt @@ -57,4 +57,4 @@ public suspend fun World.awaitChunkAt(location: Location, gen: Boolean = true): * @return The chunk retrieved. */ public suspend fun World.awaitChunkAt(x: Int, z: Int, gen: Boolean = true, urgent: Boolean = false): Chunk = - getChunkAtAsync(x, z, gen, urgent).await() \ No newline at end of file + getChunkAtAsync(x, z, gen, urgent).await() diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/event/_Cancellable.kt b/src/main/kotlin/com/github/rushyverse/api/extension/event/_Cancellable.kt index 0bf15aee..9f593e97 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/event/_Cancellable.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/event/_Cancellable.kt @@ -8,3 +8,12 @@ import org.bukkit.event.Cancellable public fun Cancellable.cancel() { isCancelled = true } + +/** + * Extension function allowing to cancel the current process by method calling with a condition. + */ +public inline fun T.cancelIf(condition: T.() -> Boolean) { + if (condition()) { + cancel() + } +} diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/event/_Listener.kt b/src/main/kotlin/com/github/rushyverse/api/extension/event/_Listener.kt index cea3e3f9..58afa84c 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/event/_Listener.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/event/_Listener.kt @@ -14,7 +14,9 @@ import kotlin.time.Duration /** * Unregister the listener. */ -public fun Listener.unregister(): Unit = HandlerList.unregisterAll(this) +public fun Listener.unregister() { + HandlerList.unregisterAll(this) +} /** * Wait events corresponding to an event type. @@ -25,7 +27,8 @@ public fun Listener.unregister(): Unit = HandlerList.unregisterAll(this) * @param plugin Java plugin to register the listener. * @param priority Priority to register this event at. * @param ignoreCancelled Whether to pass canceled events or not. - * @param block Function to treat the received event, returns `true` to valid the event and stop the listening, `false` otherwise. + * @param block Function to treat the received event, + * returns `true` to valid the event and stop the listening, `false` otherwise. */ public suspend inline fun waitEvent( plugin: JavaPlugin, @@ -52,7 +55,8 @@ public suspend inline fun waitEvent( * @param plugin Java plugin to register the listener. * @param priority Priority to register this event at. * @param ignoreCancelled Whether to pass canceled events or not. - * @param block Function to treat the received event, returns `true` to valid the event and stop the listening, `false` otherwise. + * @param block Function to treat the received event, + * returns `true` to valid the event and stop the listening, `false` otherwise. */ public suspend inline fun waitEvent( plugin: JavaPlugin, diff --git a/src/main/kotlin/com/github/rushyverse/api/game/GameData.kt b/src/main/kotlin/com/github/rushyverse/api/game/GameData.kt index c8575a83..0266b918 100644 --- a/src/main/kotlin/com/github/rushyverse/api/game/GameData.kt +++ b/src/main/kotlin/com/github/rushyverse/api/game/GameData.kt @@ -1,8 +1,16 @@ package com.github.rushyverse.api.game +/** + * Represents the data of a game. + * This class can be used to communicate any game information across the framework. + * @param type The type of the game. + * @param id The id of the game. + * @param players The players count in the game. + * @param state The state of the game. It is [GameState.WAITING] by default. + */ public data class GameData( val type: String, val id: Int, var players: Int = 0, var state: GameState = GameState.WAITING, -) \ No newline at end of file +) diff --git a/src/main/kotlin/com/github/rushyverse/api/game/GameState.kt b/src/main/kotlin/com/github/rushyverse/api/game/GameState.kt index 94f43f16..7029f34e 100644 --- a/src/main/kotlin/com/github/rushyverse/api/game/GameState.kt +++ b/src/main/kotlin/com/github/rushyverse/api/game/GameState.kt @@ -1,10 +1,33 @@ package com.github.rushyverse.api.game +/** + * Represents the various states a game can be in at any given moment. + * Each state corresponds to a different phase in the game's lifecycle. + */ public enum class GameState { + /** + * Represents the state where the game is waiting for necessary conditions to start. + * This could be waiting for more players to join, or waiting for some setup process to finish. + */ WAITING, + + /** + * Represents the state when the game is in the process of starting. + * This is a transitional phase, initialization of game resources, + * a countdown timer before the game starts, etc. + */ STARTING, + + /** + * Represents the state where the game has officially started. + * Gameplay is active during this state. + */ STARTED, - ENDING - ; -} \ No newline at end of file + + /** + * Represents the state when the game is in the process of ending. + * This is a transitional phase, where final scores might be calculated, game resources might be cleaned up, etc. + */ + ENDING; +} diff --git a/src/main/kotlin/com/github/rushyverse/api/game/SharedGameData.kt b/src/main/kotlin/com/github/rushyverse/api/game/SharedGameData.kt index 462ca0f8..f3183603 100644 --- a/src/main/kotlin/com/github/rushyverse/api/game/SharedGameData.kt +++ b/src/main/kotlin/com/github/rushyverse/api/game/SharedGameData.kt @@ -1,46 +1,85 @@ package com.github.rushyverse.api.game +/** + * Represents a shared container for game data across multiple games. + * It provides utility functions to fetch player count, game state, and + * allows listeners to be notified of changes in the game data. + */ public class SharedGameData { + /** + * A list to hold the data for all active games. + */ public val games: MutableList = mutableListOf() + + // A set of listeners that get called when there's a change in the game data. private val onChange: MutableSet<() -> Unit> = mutableSetOf() /** - * Get the total of players into games + * Calculates the total number of players across all games. + * + * @return Total number of players. */ public fun players(): Int = games.sumOf { it.players } /** - * Get the total of players into a specific type of games + * Calculates the total number of players in a specific type of game. + * + * @param gameType The type of game to filter by. + * @return Total number of players for the specified game type. */ public fun players(gameType: String): Int = games.filter { it.type == gameType }.sumOf { it.players } /** - * Get the players of a specific game + * Retrieves the number of players in a specific game identified by its type and ID. + * + * @param gameType The type of game. + * @param gameId The unique ID of the game. + * @return Number of players in the specified game, or 0 if the game is not found. */ public fun players(gameType: String, gameId: Int): Int = games.firstOrNull { it.type == gameType && it.id == gameId }?.players ?: 0 /** - * Get the state of a specific game + * Retrieves the state of a specific game identified by its type and ID. + * + * @param gameType The type of game. + * @param gameId The unique ID of the game. + * @return GameState of the specified game, or GameState.WAITING if the game is not found. */ public fun state(gameType: String, gameId: Int): GameState = games.distinctBy { it.type == gameType }.firstOrNull { it.id == gameId }?.state ?: GameState.WAITING - /** - * Count all games of a specific type of game + * Counts the number of games for a specific type. + * + * @param gameType The type of game to filter by. + * @return The count of games of the specified type. */ - public fun games(gameType: String): Int = games.filter { it.type == gameType }.size + public fun games(gameType: String): Int = games.count { it.type == gameType } + /** + * Subscribes a listener that gets called when the game data changes. + * + * @param unit The callback function to be invoked upon changes. + */ public fun subscribeOnChange(unit: () -> Unit) { onChange.add(unit) } + /** + * Invokes all the registered listeners to notify about a change in game data. + */ public fun callOnChange() { onChange.forEach { it.invoke() } } + /** + * Updates an existing game's data or adds a new game to the list. + * After, it notifies all registered listeners about the change. + * + * @param gameData The game data to be updated or added. + */ public fun saveUpdate(gameData: GameData) { val index = games.indexOf(gameData) if (index == -1) { diff --git a/src/main/kotlin/com/github/rushyverse/api/game/stats/KillableStats.kt b/src/main/kotlin/com/github/rushyverse/api/game/stats/KillableStats.kt index 319349bd..7b9b7ed9 100644 --- a/src/main/kotlin/com/github/rushyverse/api/game/stats/KillableStats.kt +++ b/src/main/kotlin/com/github/rushyverse/api/game/stats/KillableStats.kt @@ -1,10 +1,20 @@ package com.github.rushyverse.api.game.stats +/** + * Represents statistics associated with players that can be killed within a game. + * These statistics include kill and death counts, and also provides a method to calculate the score based on them. + */ public open class KillableStats( + /** + * The number of kills achieved by the entity/player. + */ public var kills: Int = 0, - public var deaths: Int = 0, - ) : Stats { + /** + * The number of times the entity/player has been killed. + */ + public var deaths: Int = 0 +) : Stats { public override fun calculateScore(): Int { val score = kills - deaths diff --git a/src/main/kotlin/com/github/rushyverse/api/game/stats/Stats.kt b/src/main/kotlin/com/github/rushyverse/api/game/stats/Stats.kt index 54eefca8..18487569 100644 --- a/src/main/kotlin/com/github/rushyverse/api/game/stats/Stats.kt +++ b/src/main/kotlin/com/github/rushyverse/api/game/stats/Stats.kt @@ -1,5 +1,16 @@ package com.github.rushyverse.api.game.stats +/** + * A functional interface that represents game statistics. + * Implementing classes/entities are expected to provide a mechanism + * to calculate a score based on their specific game-related stats. + */ public fun interface Stats { + + /** + * Calculates the score based on the implementing class's/game entity's statistics. + * + * @return The calculated score as an integer value. + */ public fun calculateScore(): Int } diff --git a/src/main/kotlin/com/github/rushyverse/api/game/stats/WinnableStats.kt b/src/main/kotlin/com/github/rushyverse/api/game/stats/WinnableStats.kt index c037febd..a9efb3cc 100644 --- a/src/main/kotlin/com/github/rushyverse/api/game/stats/WinnableStats.kt +++ b/src/main/kotlin/com/github/rushyverse/api/game/stats/WinnableStats.kt @@ -1,7 +1,17 @@ package com.github.rushyverse.api.game.stats +/** + * Represents statistics associated with entities or players that participate in win-lose scenarios within a game. + * These statistics include win and loss counts, and also provides a method to calculate the score based on them. + */ public open class WinnableStats( + /** + * The number of times the player has won. + */ public var wins: Int = 0, + /** + * The number of times the player has lost. + */ public var loses: Int = 0 ) : Stats { @@ -11,4 +21,4 @@ public open class WinnableStats( return 0 return score } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt b/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt index f6b63459..b25a4a62 100644 --- a/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt +++ b/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt @@ -3,15 +3,19 @@ package com.github.rushyverse.api.game.team import com.github.rushyverse.api.APIPlugin.Companion.BUNDLE_API import com.github.rushyverse.api.translation.SupportedLanguage import com.github.rushyverse.api.translation.TranslationProvider -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.Component.text import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.TextColor import java.util.* +/** + * Enum that defines the supported team types for a game, each associated with a specific color. + * + * @property color The color associated with the team, represented by a `TextColor` object. + */ public enum class TeamType( public val color: TextColor ) { + WHITE(NamedTextColor.WHITE), RED(NamedTextColor.RED), BLUE(NamedTextColor.BLUE), @@ -20,35 +24,17 @@ public enum class TeamType( PURPLE(NamedTextColor.LIGHT_PURPLE), AQUA(NamedTextColor.AQUA), ORANGE(NamedTextColor.GOLD), - BLACK(NamedTextColor.BLACK) - ; - + BLACK(NamedTextColor.BLACK); + + /** + * Provides the translated name of the team based on the provided locale. + * + * @param translator The translation provider that fetches translations from a bundle of files. + * @param locale The target locale for the translation, with a default of English. + * @return The translated name of the team. + */ public fun name( - translationProvider: TranslationProvider, - locale: Locale = SupportedLanguage.ENGLISH.locale - ): String = translationProvider.translate("team.${name.lowercase()}", locale, BUNDLE_API) - - public fun textName( - translationProvider: TranslationProvider, - locale: Locale = SupportedLanguage.ENGLISH.locale - ): Component = text(name(translationProvider, locale)) - - public fun memberAdjective( - translationProvider: TranslationProvider, - locale: Locale = SupportedLanguage.ENGLISH.locale - ): String { - val key = "team.${name}.member" - val result = translationProvider.translate(key, locale, BUNDLE_API) - - if (result == key) { - return name(translationProvider, locale) - } - - return result - } - - public fun textMemberAdjective( - translationProvider: TranslationProvider, + translator: TranslationProvider, locale: Locale = SupportedLanguage.ENGLISH.locale - ): Component = text(memberAdjective(translationProvider, locale)) + ): String = translator.translate("team.${name.lowercase()}", locale, BUNDLE_API) } diff --git a/src/main/kotlin/com/github/rushyverse/api/item/CraftBuilder.kt b/src/main/kotlin/com/github/rushyverse/api/item/CraftBuilder.kt index 1965d369..ca7e3ac2 100644 --- a/src/main/kotlin/com/github/rushyverse/api/item/CraftBuilder.kt +++ b/src/main/kotlin/com/github/rushyverse/api/item/CraftBuilder.kt @@ -9,6 +9,21 @@ import org.bukkit.inventory.ShapedRecipe import kotlin.contracts.InvocationKind import kotlin.contracts.contract +/** + * Width of the craft table. + */ +private const val CRAFT_TABLE_WIDTH = 3 + +/** + * Height of the craft table. + */ +private const val CRAFT_TABLE_HEIGHT = 3 + +/** + * Size of the craft table. + */ +private const val CRAFT_TABLE_SIZE = CRAFT_TABLE_WIDTH * CRAFT_TABLE_HEIGHT + /** * Slots with index corresponding to the index defined in minecraft for the craft table. * @property index Index in the craft table. @@ -72,7 +87,7 @@ public class CraftBuilder { * Storage of the item with the position assigned for the recipe's shape. * The top left position is defined by 0 and bottom right 8 */ - private val craft: Array = arrayOfNulls(9) + private val craft: Array = arrayOfNulls(CRAFT_TABLE_SIZE) /** * Result item of the craft. @@ -194,7 +209,7 @@ public class CraftBuilder { // ["abcdefghi"] .joinToString(separator = "") // ["abc", "def", "ghi"] - .chunked(3) + .chunked(CRAFT_TABLE_WIDTH) .toTypedArray() /** diff --git a/src/main/kotlin/com/github/rushyverse/api/item/exception/CraftResultMissingException.kt b/src/main/kotlin/com/github/rushyverse/api/item/exception/CraftResultMissingException.kt index 4d784b3b..475327b6 100644 --- a/src/main/kotlin/com/github/rushyverse/api/item/exception/CraftResultMissingException.kt +++ b/src/main/kotlin/com/github/rushyverse/api/item/exception/CraftResultMissingException.kt @@ -3,4 +3,4 @@ package com.github.rushyverse.api.item.exception /** * Exception when the result item is not defined. */ -public class CraftResultMissingException : IllegalStateException("The result item for the craft must be defined") \ No newline at end of file +public class CraftResultMissingException : IllegalStateException("The result item for the craft must be defined") diff --git a/src/main/kotlin/com/github/rushyverse/api/koin/CraftContext.kt b/src/main/kotlin/com/github/rushyverse/api/koin/CraftContext.kt index 09cd1e76..ca6f8cbb 100644 --- a/src/main/kotlin/com/github/rushyverse/api/koin/CraftContext.kt +++ b/src/main/kotlin/com/github/rushyverse/api/koin/CraftContext.kt @@ -91,10 +91,12 @@ public object CraftContext { public fun getOrNull(id: String): Koin? = _koins[id]?.second /** Closes and removes the current [Koin] instance. */ - public fun stopKoin(id: String): Unit = synchronized(this) { - val koinInstance = _koins[id] ?: return@synchronized - koinInstance.second.close() - _koins -= id + public fun stopKoin(id: String) { + synchronized(this) { + val koinInstance = _koins[id] ?: return@synchronized + koinInstance.second.close() + _koins -= id + } } /** @@ -143,8 +145,10 @@ public object CraftContext { * * @param module The module to load. */ - public fun loadKoinModules(id: String, module: Module): Unit = synchronized(this) { - loadKoinModules(id, listOf(module)) + public fun loadKoinModules(id: String, module: Module) { + synchronized(this) { + loadKoinModules(id, listOf(module)) + } } /** @@ -152,8 +156,10 @@ public object CraftContext { * * @param modules The modules to load. */ - public fun loadKoinModules(id: String, modules: List): Unit = synchronized(this) { - get(id).loadModules(modules) + public fun loadKoinModules(id: String, modules: List) { + synchronized(this) { + get(id).loadModules(modules) + } } /** @@ -161,8 +167,10 @@ public object CraftContext { * * @param module The module to unload. */ - public fun unloadKoinModules(id: String, module: Module): Unit = synchronized(this) { - unloadKoinModules(id, listOf(module)) + public fun unloadKoinModules(id: String, module: Module) { + synchronized(this) { + unloadKoinModules(id, listOf(module)) + } } /** @@ -170,7 +178,9 @@ public object CraftContext { * * @param modules The modules to unload. */ - public fun unloadKoinModules(id: String, modules: List): Unit = synchronized(this) { - get(id).unloadModules(modules) + public fun unloadKoinModules(id: String, modules: List) { + synchronized(this) { + get(id).unloadModules(modules) + } } } diff --git a/src/main/kotlin/com/github/rushyverse/api/player/Client.kt b/src/main/kotlin/com/github/rushyverse/api/player/Client.kt index 16f62535..7e0b49c9 100644 --- a/src/main/kotlin/com/github/rushyverse/api/player/Client.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/Client.kt @@ -1,6 +1,7 @@ package com.github.rushyverse.api.player import com.github.rushyverse.api.delegate.DelegatePlayer +import com.github.rushyverse.api.extension.asComponent import com.github.rushyverse.api.koin.inject import com.github.rushyverse.api.player.exception.PlayerNotFoundException import com.github.rushyverse.api.player.scoreboard.ScoreboardManager @@ -8,18 +9,21 @@ import com.github.rushyverse.api.translation.SupportedLanguage import fr.mrmicky.fastboard.adventure.FastBoard import kotlinx.coroutines.CoroutineScope import net.kyori.adventure.text.Component -import net.kyori.adventure.text.Component.text +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver import org.bukkit.entity.Player import java.util.* /** - * Client to store and manage data about player.* + * Client to store and manage data about player. * @property playerUUID Player's uuid. * @property player Player linked to the client. */ public open class Client( public val playerUUID: UUID, coroutineScope: CoroutineScope, + /** + * The current language of the player. + */ public var lang: SupportedLanguage = SupportedLanguage.ENGLISH ) : CoroutineScope by coroutineScope { @@ -35,9 +39,28 @@ public open class Client( public fun requirePlayer(): Player = player ?: throw PlayerNotFoundException("The player cannot be retrieved from the server") - public fun send(text: Component): Unit = requirePlayer().sendMessage(text) + /** + * Send a message to the player. + * @param text The message as component. + */ + public fun send(text: Component) { + requirePlayer().sendMessage(text) + } + + /** + * Send a message to the player. + * The message will be converted to [Component] with the standard [TagResolver] of MiniMessage. + * ``` + * // Example + * send("Hello $playerName") + * ``` + * + * @param message The string message. + */ + public fun send(message: String) { + send(message.asComponent()) + } - public fun send(message: String): Unit = send(text(message)) /** * Retrieve the scoreboard of the player. diff --git a/src/main/kotlin/com/github/rushyverse/api/player/ClientManagerImpl.kt b/src/main/kotlin/com/github/rushyverse/api/player/ClientManagerImpl.kt index b43c02f4..23aa19de 100644 --- a/src/main/kotlin/com/github/rushyverse/api/player/ClientManagerImpl.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/ClientManagerImpl.kt @@ -57,11 +57,6 @@ public class ClientManagerImpl : ClientManager { */ private fun getKey(p: Player): String = p.name - /** - * Check if a client is linked to a player. - * @param player Player - * @return `true` if there is a client for the player, `false` otherwise. - */ override suspend fun contains(player: Player): Boolean = mutex.withLock { _clients.containsKey(getKey(player)) } diff --git a/src/main/kotlin/com/github/rushyverse/api/player/exception/ClientNotFoundException.kt b/src/main/kotlin/com/github/rushyverse/api/player/exception/ClientNotFoundException.kt index b52516b6..8bb132eb 100644 --- a/src/main/kotlin/com/github/rushyverse/api/player/exception/ClientNotFoundException.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/exception/ClientNotFoundException.kt @@ -3,4 +3,4 @@ package com.github.rushyverse.api.player.exception /** * Exception if a client cannot be found. */ -public class ClientNotFoundException(message: String? = null) : RuntimeException(message) \ No newline at end of file +public class ClientNotFoundException(message: String? = null) : RuntimeException(message) diff --git a/src/main/kotlin/com/github/rushyverse/api/player/exception/PlayerNotFoundException.kt b/src/main/kotlin/com/github/rushyverse/api/player/exception/PlayerNotFoundException.kt index 7b94edd4..aa1c8c52 100644 --- a/src/main/kotlin/com/github/rushyverse/api/player/exception/PlayerNotFoundException.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/exception/PlayerNotFoundException.kt @@ -3,4 +3,4 @@ package com.github.rushyverse.api.player.exception /** * Exception if a player is not into the server. */ -public class PlayerNotFoundException(message: String? = null) : RuntimeException(message) \ No newline at end of file +public class PlayerNotFoundException(message: String? = null) : RuntimeException(message) diff --git a/src/main/kotlin/com/github/rushyverse/api/player/scoreboard/ScoreboardManager.kt b/src/main/kotlin/com/github/rushyverse/api/player/scoreboard/ScoreboardManager.kt index e5d4c043..344ecbb2 100644 --- a/src/main/kotlin/com/github/rushyverse/api/player/scoreboard/ScoreboardManager.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/scoreboard/ScoreboardManager.kt @@ -5,20 +5,46 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.bukkit.entity.Player +/** + * Manages the scoreboards for players within the game. + * This class ensures thread-safe operations on the scoreboards by using mutex locks. + */ public class ScoreboardManager { + /** + * Mutex used to ensure thread-safe operations on the scoreboards map. + */ private val mutex = Mutex() + /** + * Private mutable map storing scoreboards associated with player names. + */ private val _scoreboards = mutableMapOf() + /** + * Public immutable view of the scoreboards map. + */ public val scoreboards: Map = _scoreboards + /** + * Retrieves the scoreboard for the specified player or creates a new one if it doesn't exist. + * This function is thread-safe and uses mutex locks to ensure atomic operations. + * + * @param player The player for whom the scoreboard is to be retrieved or created. + * @return The scoreboard associated with the player. + */ public suspend fun getOrCreate(player: Player): FastBoard = mutex.withLock { _scoreboards.getOrPut(player.name) { FastBoard(player) } } + /** + * Removes and deletes the scoreboard associated with the specified player. + * This function is thread-safe and uses mutex locks to ensure atomic operations. + * + * @param player The player whose scoreboard is to be removed. + */ public suspend fun remove(player: Player) { mutex.withLock { _scoreboards.remove(player.name) }?.delete() } diff --git a/src/main/kotlin/com/github/rushyverse/api/schedule/Scheduler.kt b/src/main/kotlin/com/github/rushyverse/api/schedule/Scheduler.kt index a2982ee9..4fe0a53c 100644 --- a/src/main/kotlin/com/github/rushyverse/api/schedule/Scheduler.kt +++ b/src/main/kotlin/com/github/rushyverse/api/schedule/Scheduler.kt @@ -26,4 +26,4 @@ public interface Scheduler { * Stop the task and wait the end of last execution. */ public suspend fun cancelAndJoin() -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/schedule/SchedulerTask.kt b/src/main/kotlin/com/github/rushyverse/api/schedule/SchedulerTask.kt index 3fea81fa..55d25767 100644 --- a/src/main/kotlin/com/github/rushyverse/api/schedule/SchedulerTask.kt +++ b/src/main/kotlin/com/github/rushyverse/api/schedule/SchedulerTask.kt @@ -200,8 +200,10 @@ public class SchedulerTask( * Throws exception if no task is present at the index. * @param index Index of the task that will be removed. */ - public suspend fun removeAt(index: Int): Unit = mutex.withLock { - removeAtUnsafe(index) + public suspend fun removeAt(index: Int) { + mutex.withLock { + removeAtUnsafe(index) + } } /** diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/EnumSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/EnumSerializer.kt index 6931c8f9..0ee31c1a 100644 --- a/src/main/kotlin/com/github/rushyverse/api/serializer/EnumSerializer.kt +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/EnumSerializer.kt @@ -46,6 +46,8 @@ public open class EnumSerializer>( public fun findEnumValue(decoded: String): T { val name = decoded.uppercase().replace(" ", "_") return values.firstOrNull { it.name.uppercase() == name } - ?: throw SerializationException("Invalid enum value: $decoded. Valid values are: ${values.joinToString { it.name }}") + ?: throw SerializationException("Invalid enum value: $decoded. Valid values are: ${ + values.joinToString { it.name } + }") } } diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/ItemStackSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/ItemStackSerializer.kt index 75b74d6b..1c282adc 100644 --- a/src/main/kotlin/com/github/rushyverse/api/serializer/ItemStackSerializer.kt +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/ItemStackSerializer.kt @@ -201,25 +201,19 @@ public object ItemStackSerializer : KSerializer { destroyableKeys?.also(it::setDestroyableKeys) placeableKeys?.also(it::setPlaceableKeys) displayName?.also(it::displayName) - lore?.toList()?.also(it::lore) + lore?.run { toList().also(it::lore) } flags?.also { itemFlags -> it.addItemFlags(*itemFlags.toTypedArray()) } when (it) { - is Damageable -> { - durability?.also(it::damage) - } + is Damageable -> durability?.also(it::damage) - is SkullMeta -> { - texture?.let { texture -> - val profile = Bukkit.createProfile(UUID.randomUUID()) - profile.setTextures(texture) - it.playerProfile = profile - } + is SkullMeta -> texture?.let { texture -> + val profile = Bukkit.createProfile(UUID.randomUUID()) + profile.setTextures(texture) + it.playerProfile = profile } - is BannerMeta -> { - patterns?.also(it::setPatterns) - } + is BannerMeta -> patterns?.also(it::setPatterns) } } } diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProvider.kt b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProvider.kt index 3894ba3e..e0774d67 100644 --- a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProvider.kt +++ b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProvider.kt @@ -23,7 +23,7 @@ private val logger = KotlinLogging.logger { } * Translation provider backed by Java's [ResourceBundle]s. This makes use of `.properties` files that are standard * across the Java ecosystem. */ -public open class ResourceBundleTranslationProvider : TranslationProvider() { +public open class ResourceBundleTranslationProvider : TranslationProvider { private val bundles: MutableMap, ResourceBundle> = mutableMapOf() @@ -40,7 +40,7 @@ public open class ResourceBundleTranslationProvider : TranslationProvider() { val string = try { get(key, locale, bundleName) } catch (e: MissingResourceException) { - logger.error("Unable to find translation for key '$key' in bundles: '$bundleName'") + logger.error("Unable to find translation for key '$key' in bundles: '$bundleName'", e) return key } diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt b/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt index 274d51df..477532f2 100644 --- a/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt +++ b/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt @@ -3,13 +3,46 @@ package com.github.rushyverse.api.translation import java.util.* /** - * List of supported locales to translate keys. + * Enumerates the set of languages supported for translation. + * + * Each entry in this enum represents a specific language and locale combination, + * providing both a human-readable display name and a [Locale] object for internal use. */ -public enum class SupportedLanguage(public val displayName: String, public val locale: Locale) { +public enum class SupportedLanguage( + /** + * A human-readable display name for the language. + */ + public val displayName: String, + /** + * Locale object representing the language and country code combination. + * This locale can be used with localization and internationalization libraries + * to format and retrieve translations. + */ + public val locale: Locale +) { + /** + * English language for the United Kingdom. + */ ENGLISH("English", Locale("en", "gb")), + + /** + * French language for France. + */ FRENCH("Français", Locale("fr", "fr")), + + /** + * Spanish language for Spain. + */ SPANISH("Español", Locale("es", "es")), + + /** + * German language for Germany. + */ GERMAN("Deutsch", Locale("de", "de")), + + /** + * Chinese language for China. + */ CHINESE("Chinese", Locale("zh", "ch")) -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/TranslationProvider.kt b/src/main/kotlin/com/github/rushyverse/api/translation/TranslationProvider.kt index 00f7af6e..f376a775 100644 --- a/src/main/kotlin/com/github/rushyverse/api/translation/TranslationProvider.kt +++ b/src/main/kotlin/com/github/rushyverse/api/translation/TranslationProvider.kt @@ -5,17 +5,17 @@ import java.util.* /** * Translation provider interface, in charge of taking string keys and returning translated strings. */ -public abstract class TranslationProvider { +public interface TranslationProvider { /** * Get a translation by key from the given locale and bundle name. */ - public abstract fun get(key: String, locale: Locale, bundleName: String): String + public fun get(key: String, locale: Locale, bundleName: String): String /** * Get a formatted translation using the provided arguments. */ - public abstract fun translate( + public fun translate( key: String, locale: Locale, bundleName: String, diff --git a/src/main/kotlin/com/github/rushyverse/api/world/CylinderArea.kt b/src/main/kotlin/com/github/rushyverse/api/world/CylinderArea.kt index 1f05b4ec..d1c0f24f 100644 --- a/src/main/kotlin/com/github/rushyverse/api/world/CylinderArea.kt +++ b/src/main/kotlin/com/github/rushyverse/api/world/CylinderArea.kt @@ -114,11 +114,40 @@ public class CylinderArea( override fun isInArea(location: Location): Boolean { val areaLocation = this.location - return location.world === areaLocation.world // Same world - && sqrt((location.x - areaLocation.x).pow(2.0) + (location.z - areaLocation.z).pow(2.0)) <= radius // Within radius - && location.y in height // Within height + return isSameWorld(location, areaLocation) + && isInRadius(location, areaLocation) + && isInHeight(location) } + /** + * Determines if two locations are in the same world. + * + * @param location The first location. + * @param areaLocation The second location. + * @return `true` if both locations are in the same world, `false` otherwise. + */ + private fun isSameWorld(location: Location, areaLocation: Location): Boolean = + location.world === areaLocation.world + + /** + * Checks if the given location is within the specified height range. + * + * @param location The location to check. + * @return `true` if the location is within the height range, `false` otherwise. + */ + private fun isInHeight(location: Location): Boolean = + location.y in height + + /** + * Determines if a given location is within a specified radius of an area location. + * + * @param location The location to check. + * @param areaLocation The area location to compare against. + * @return `true` if the location is within the radius of the area location, `false` otherwise. + */ + private fun isInRadius(location: Location, areaLocation: Location): Boolean = + sqrt((location.x - areaLocation.x).pow(2.0) + (location.z - areaLocation.z).pow(2.0)) <= radius + override fun toString(): String { return "CylinderArea(location=$location, height=$height, radius=$radius)" } diff --git a/src/main/kotlin/com/github/rushyverse/api/world/exception/WorldDifferentException.kt b/src/main/kotlin/com/github/rushyverse/api/world/exception/WorldDifferentException.kt index ade4c9fa..cbeeaa1e 100644 --- a/src/main/kotlin/com/github/rushyverse/api/world/exception/WorldDifferentException.kt +++ b/src/main/kotlin/com/github/rushyverse/api/world/exception/WorldDifferentException.kt @@ -3,11 +3,20 @@ package com.github.rushyverse.api.world.exception import org.bukkit.World /** - * Exception when two worlds are different. + * Represents an exception that is thrown when two provided worlds are not the same or are incompatible + * for a specific operation. + * + * This can be used in contexts where operations rely on the assumption that entities, objects, or + * other items belong to the same world, and any mismatch would result in unexpected behavior. + * + * @property world1 The first world involved in the check, which may be the source or origin. + * @property world2 The second world involved in the check, which may be the destination or target. + * @param message Optional error message detailing the context or reason for the exception. + * If not provided, a default message may be utilized. */ public class WorldDifferentException( public val world1: World? = null, public val world2: World? = null, message: String? = null ) : - RuntimeException(message) \ No newline at end of file + RuntimeException(message ?: "The worlds ${world1?.name} and ${world2?.name} are different.") diff --git a/src/main/kotlin/com/github/rushyverse/api/world/exception/WorldNotFoundException.kt b/src/main/kotlin/com/github/rushyverse/api/world/exception/WorldNotFoundException.kt index 69cc4e49..0de63f1a 100644 --- a/src/main/kotlin/com/github/rushyverse/api/world/exception/WorldNotFoundException.kt +++ b/src/main/kotlin/com/github/rushyverse/api/world/exception/WorldNotFoundException.kt @@ -3,4 +3,4 @@ package com.github.rushyverse.api.world.exception /** * Exception if a world is not into the server. */ -public class WorldNotFoundException(message: String? = null) : RuntimeException(message) \ No newline at end of file +public class WorldNotFoundException(message: String? = null) : RuntimeException(message) diff --git a/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt b/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt index a3599ad8..4a7ea6aa 100644 --- a/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/AbstractKoinTest.kt @@ -10,7 +10,7 @@ import org.koin.dsl.ModuleDeclaration import kotlin.test.AfterTest import kotlin.test.BeforeTest -abstract class AbstractKoinTest { +open class AbstractKoinTest { lateinit var plugin: Plugin diff --git a/src/test/kotlin/com/github/rushyverse/api/delegate/DelegateWorldTest.kt b/src/test/kotlin/com/github/rushyverse/api/delegate/DelegateWorldTest.kt index 2d22c2ca..9ce52636 100644 --- a/src/test/kotlin/com/github/rushyverse/api/delegate/DelegateWorldTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/delegate/DelegateWorldTest.kt @@ -13,9 +13,7 @@ class DelegateWorldTest { @BeforeTest fun onBefore() { world = WorldMock() - MockBukkit.mock().apply { - addWorld(world) - } + MockBukkit.mock().addWorld(world) } @AfterTest diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/CoroutineScopeExt.kt b/src/test/kotlin/com/github/rushyverse/api/extension/CoroutineScopeExt.kt index 969c0f50..115078f1 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/CoroutineScopeExt.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/CoroutineScopeExt.kt @@ -54,4 +54,4 @@ class CoroutineScopeExt { scope.coroutineContext.cancelChildren() assertFalse { scheduler.running } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/DurationExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/DurationExtTest.kt index 358edb72..cbedeab7 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/DurationExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/DurationExtTest.kt @@ -111,4 +111,4 @@ class DurationExtTest { assertEquals(250.milliseconds, 5.toULong().ticks) } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/RunnableExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/RunnableExtTest.kt index 5eaede52..1bfc9ae0 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/RunnableExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/RunnableExtTest.kt @@ -14,4 +14,4 @@ class RunnableExtTest { runnable.run() assertTrue { isCalled } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt index f80dd207..ecf04cf7 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt @@ -14,26 +14,6 @@ import kotlin.test.assertNull class StringExtTest { - @Nested - @DisplayName("Colored sentence") - inner class Colored { - - @Test - fun `no change if there is no specific char`() { - fun verifyIntegrity(sentence: String) { - assertEquals(sentence, sentence.colored()) - } - verifyIntegrity("Hello minecraft world !") - verifyIntegrity("§4It's a good §6day.") - } - - @Test - fun `change applied if there is specific char`() { - assertEquals("§1Hello §2minecraft §3world !", "&1Hello &2minecraft &3world !".colored()) - assertEquals("§4It's a good day.", "&4It's a good day.".colored()) - } - } - @Nested @DisplayName("Base64") inner class Base64Test { @@ -70,7 +50,12 @@ class StringExtTest { } @ParameterizedTest - @ValueSource(strings = ["", "a", "c7e4ca3236d942408e53de44bef8eeeb", "c7e4ca32-36d9-4240-8e53-de44bef8eeeba"]) + @ValueSource(strings = [ + "", + "a", + "c7e4ca3236d942408e53de44bef8eeeb", + "c7e4ca32-36d9-4240-8e53-de44bef8eeeba" + ]) fun `throws exception if invalid`(value: String) { assertThrows { value.toUUIDStrict() @@ -85,7 +70,12 @@ class StringExtTest { } @ParameterizedTest - @ValueSource(strings = ["", "a", "c7e4ca3236d942408e53de44bef8eeeb", "c7e4ca32-36d9-4240-8e53-de44bef8eeeba"]) + @ValueSource(strings = [ + "", + "a", + "c7e4ca3236d942408e53de44bef8eeeb", + "c7e4ca32-36d9-4240-8e53-de44bef8eeeba" + ]) fun `nulls if invalid`(value: String) { assertNull(value.toUUIDStrictOrNull()) } @@ -96,14 +86,22 @@ class StringExtTest { inner class NoStrict { @ParameterizedTest - @ValueSource(strings = ["c7e4ca3236d942408e53de44bef8eeeb", "c7e4ca32-36d9-4240-8e53-de44bef8eeeb"]) + @ValueSource(strings = [ + "c7e4ca3236d942408e53de44bef8eeeb", + "c7e4ca32-36d9-4240-8e53-de44bef8eeeb" + ]) fun `can convert if the string is valid`(value: String) { val uuid = UUID.fromString("c7e4ca32-36d9-4240-8e53-de44bef8eeeb") assertEquals(uuid, value.toUUID()) } @ParameterizedTest - @ValueSource(strings = ["", "a", "c7e4ca3236d942408e53de44bef8eeeba", "c7e4ca32-36d9-4240-8e53-de44bef8eeeba"]) + @ValueSource(strings = [ + "", + "a", + "c7e4ca3236d942408e53de44bef8eeeba", + "c7e4ca32-36d9-4240-8e53-de44bef8eeeba" + ]) fun `throws exception if invalid`(value: String) { assertThrows { value.toUUID() @@ -118,7 +116,12 @@ class StringExtTest { } @ParameterizedTest - @ValueSource(strings = ["", "a", "c7e4ca3236d942408e53de44bef8eeeba", "c7e4ca32-36d9-4240-8e53-de44bef8eeeba"]) + @ValueSource(strings = [ + "", + "a", + "c7e4ca3236d942408e53de44bef8eeeba", + "c7e4ca32-36d9-4240-8e53-de44bef8eeeba" + ]) fun `nulls if invalid`(value: String) { assertNull(value.toUUIDStrictOrNull()) } diff --git a/src/test/kotlin/com/github/rushyverse/api/serializer/EnchantmentSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/serializer/EnchantmentSerializerTest.kt index c33a2db6..85593543 100644 --- a/src/test/kotlin/com/github/rushyverse/api/serializer/EnchantmentSerializerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/serializer/EnchantmentSerializerTest.kt @@ -22,9 +22,10 @@ import kotlin.test.Test class EnchantmentSerializerTest { - class EnchantmentMock(private val _name: String, namespace: NamespacedKey) : Enchantment(namespace) { + class EnchantmentMock(private val enchantmentName: String, namespace: NamespacedKey) : Enchantment(namespace) { override fun translationKey(): String = error("Not implemented") - override fun getName(): String = _name + @Deprecated("Deprecated in Java") + override fun getName(): String = enchantmentName override fun getMaxLevel(): Int = error("Not implemented") override fun getStartLevel(): Int = error("Not implemented") override fun getItemTarget(): EnchantmentTarget = error("Not implemented") @@ -149,7 +150,8 @@ class EnchantmentSerializerTest { Json.decodeFromString(EnchantmentSerializer, json) } - exception.message shouldBe "Unable to find enchantment with namespaced key: $namespace:$key. Valid enchantments are: minecraft:impaling, minecraft:thorns, minecraft:piercing, minecraft:fire_protection, minecraft:smite, minecraft:unbreaking, minecraft:swift_sneak, minecraft:feather_falling, minecraft:mending, minecraft:protection, minecraft:respiration, minecraft:projectile_protection, minecraft:knockback, minecraft:fire_aspect, minecraft:luck_of_the_sea, minecraft:lure, minecraft:punch, minecraft:channeling, minecraft:frost_walker, minecraft:sharpness, minecraft:power, minecraft:riptide, minecraft:bane_of_arthropods, minecraft:efficiency, minecraft:fortune, minecraft:looting, minecraft:loyalty, minecraft:silk_touch, minecraft:quick_charge, minecraft:binding_curse, minecraft:aqua_affinity, minecraft:multishot, minecraft:depth_strider, minecraft:vanishing_curse, minecraft:infinity, minecraft:flame, minecraft:blast_protection, minecraft:sweeping" + exception.message shouldBe "Unable to find enchantment with namespaced key: $namespace:$key. " + + "Valid enchantments are: ${Enchantment.values().joinToString(", ") { it.key.toString() }}" } } diff --git a/src/test/kotlin/com/github/rushyverse/api/serializer/LocationSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/serializer/LocationSerializerTest.kt index febed40b..6ecd18fe 100644 --- a/src/test/kotlin/com/github/rushyverse/api/serializer/LocationSerializerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/serializer/LocationSerializerTest.kt @@ -22,9 +22,7 @@ class LocationSerializerTest { @BeforeTest fun onBefore() { world = WorldMock() - MockBukkit.mock().apply { - addWorld(world) - } + MockBukkit.mock().addWorld(world) } @AfterTest diff --git a/src/test/kotlin/com/github/rushyverse/api/serializer/RangeDoubleSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/serializer/RangeDoubleSerializerTest.kt index ff48fe40..3459a741 100644 --- a/src/test/kotlin/com/github/rushyverse/api/serializer/RangeDoubleSerializerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/serializer/RangeDoubleSerializerTest.kt @@ -66,7 +66,7 @@ class RangeDoubleSerializerTest { "end": $end } """.trimIndent() - Json.decodeFromString(RangeDoubleSerializer, json) shouldBe (start..end) + Json.decodeFromString(RangeDoubleSerializer, json) shouldBe start..end } } diff --git a/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt b/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt index 0274c3d4..46099a9c 100644 --- a/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt +++ b/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt @@ -2,7 +2,6 @@ package com.github.rushyverse.api.utils import org.bukkit.Location import org.bukkit.World -import java.util.* import kotlin.random.Random fun randomString( diff --git a/src/test/kotlin/com/github/rushyverse/api/utils/LocationTestUtils.kt b/src/test/kotlin/com/github/rushyverse/api/utils/LocationTestUtils.kt index cde981f8..a1eb9923 100644 --- a/src/test/kotlin/com/github/rushyverse/api/utils/LocationTestUtils.kt +++ b/src/test/kotlin/com/github/rushyverse/api/utils/LocationTestUtils.kt @@ -12,4 +12,4 @@ fun assertEqualsLocation(loc1: Location, loc2: Location) { assertEquals(yaw, loc2.yaw) assertEquals(pitch, loc2.pitch) } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaSerializerTest.kt index e58531f9..0e94fa94 100644 --- a/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaSerializerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaSerializerTest.kt @@ -22,9 +22,7 @@ class CubeAreaSerializerTest { @BeforeTest fun onBefore() { world = WorldMock() - MockBukkit.mock().apply { - addWorld(world) - } + MockBukkit.mock().addWorld(world) } @AfterTest diff --git a/src/test/kotlin/com/github/rushyverse/api/world/cylinder/CylinderAreaSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/cylinder/CylinderAreaSerializerTest.kt index 682773b7..f23a9549 100644 --- a/src/test/kotlin/com/github/rushyverse/api/world/cylinder/CylinderAreaSerializerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/world/cylinder/CylinderAreaSerializerTest.kt @@ -22,9 +22,7 @@ class CylinderAreaSerializerTest { @BeforeTest fun onBefore() { world = WorldMock() - MockBukkit.mock().apply { - addWorld(world) - } + MockBukkit.mock().addWorld(world) } @AfterTest diff --git a/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaSerializerTest.kt index 031201ec..6d83096f 100644 --- a/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaSerializerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/world/sphere/SphereAreaSerializerTest.kt @@ -22,9 +22,7 @@ class SphereAreaSerializerTest { @BeforeTest fun onBefore() { world = WorldMock() - MockBukkit.mock().apply { - addWorld(world) - } + MockBukkit.mock().addWorld(world) } @AfterTest From 31b8a2716f0c9386f88b84b83ecc26b94c671490 Mon Sep 17 00:00:00 2001 From: Distractic Date: Mon, 7 Aug 2023 07:57:18 +0200 Subject: [PATCH 129/143] fix(detekt): Fix quality code --- .../github/rushyverse/api/extension/NumberExtTest.kt | 12 +++++++----- .../api/extension/event/CancellableExtTest.kt | 1 - 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/NumberExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/NumberExtTest.kt index ff3bd935..61469e0f 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/NumberExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/NumberExtTest.kt @@ -85,12 +85,14 @@ class NumberExtTest { @Test fun `should return complex roman numerals`() { - NumberExtTest::class.java.getResourceAsStream("/cases/roman/numerals.txt")!!.bufferedReader().useLines { lines -> - lines.forEach { line -> - val (number, expected) = line.split(" ") - number.toInt().toRomanNumerals() shouldBe expected + NumberExtTest::class.java.getResourceAsStream("/cases/roman/numerals.txt")!! + .bufferedReader() + .useLines { lines -> + lines.forEach { line -> + val (number, expected) = line.split(" ") + number.toInt().toRomanNumerals() shouldBe expected + } } - } } } } diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/event/CancellableExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/event/CancellableExtTest.kt index ba193e21..76a45d62 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/event/CancellableExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/event/CancellableExtTest.kt @@ -1,6 +1,5 @@ package com.github.rushyverse.api.extension.event -import com.github.rushyverse.api.extension.event.cancel import io.kotest.matchers.shouldBe import org.bukkit.event.Cancellable import kotlin.test.Test From 2375f707f37acb7ae57a4bd5086620391d65ddc4 Mon Sep 17 00:00:00 2001 From: Distractic Date: Mon, 7 Aug 2023 08:15:30 +0200 Subject: [PATCH 130/143] tests(number-roman): Use CSV file source --- .../rushyverse/api/extension/NumberExtTest.kt | 15 +- src/test/resources/cases/roman/numerals.csv | 3999 +++++++++++++++++ src/test/resources/cases/roman/numerals.txt | 3999 ----------------- 3 files changed, 4004 insertions(+), 4009 deletions(-) create mode 100644 src/test/resources/cases/roman/numerals.csv delete mode 100644 src/test/resources/cases/roman/numerals.txt diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/NumberExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/NumberExtTest.kt index 61469e0f..d16991c3 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/NumberExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/NumberExtTest.kt @@ -4,6 +4,7 @@ import io.kotest.matchers.shouldBe import org.junit.jupiter.api.Nested import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvFileSource import org.junit.jupiter.params.provider.ValueSource import kotlin.test.Test @@ -83,16 +84,10 @@ class NumberExtTest { number.toRomanNumerals() shouldBe expected } - @Test - fun `should return complex roman numerals`() { - NumberExtTest::class.java.getResourceAsStream("/cases/roman/numerals.txt")!! - .bufferedReader() - .useLines { lines -> - lines.forEach { line -> - val (number, expected) = line.split(" ") - number.toInt().toRomanNumerals() shouldBe expected - } - } + @ParameterizedTest + @CsvFileSource(resources = ["/cases/roman/numerals.csv"]) + fun `should return complex roman numerals`(number: Int, expectedRoman: String) { + number.toRomanNumerals() shouldBe expectedRoman } } } diff --git a/src/test/resources/cases/roman/numerals.csv b/src/test/resources/cases/roman/numerals.csv new file mode 100644 index 00000000..43dcb471 --- /dev/null +++ b/src/test/resources/cases/roman/numerals.csv @@ -0,0 +1,3999 @@ +1, I +2, II +3, III +4, IV +5, V +6, VI +7, VII +8, VIII +9, IX +10, X +11, XI +12, XII +13, XIII +14, XIV +15, XV +16, XVI +17, XVII +18, XVIII +19, XIX +20, XX +21, XXI +22, XXII +23, XXIII +24, XXIV +25, XXV +26, XXVI +27, XXVII +28, XXVIII +29, XXIX +30, XXX +31, XXXI +32, XXXII +33, XXXIII +34, XXXIV +35, XXXV +36, XXXVI +37, XXXVII +38, XXXVIII +39, XXXIX +40, XL +41, XLI +42, XLII +43, XLIII +44, XLIV +45, XLV +46, XLVI +47, XLVII +48, XLVIII +49, XLIX +50, L +51, LI +52, LII +53, LIII +54, LIV +55, LV +56, LVI +57, LVII +58, LVIII +59, LIX +60, LX +61, LXI +62, LXII +63, LXIII +64, LXIV +65, LXV +66, LXVI +67, LXVII +68, LXVIII +69, LXIX +70, LXX +71, LXXI +72, LXXII +73, LXXIII +74, LXXIV +75, LXXV +76, LXXVI +77, LXXVII +78, LXXVIII +79, LXXIX +80, LXXX +81, LXXXI +82, LXXXII +83, LXXXIII +84, LXXXIV +85, LXXXV +86, LXXXVI +87, LXXXVII +88, LXXXVIII +89, LXXXIX +90, XC +91, XCI +92, XCII +93, XCIII +94, XCIV +95, XCV +96, XCVI +97, XCVII +98, XCVIII +99, XCIX +100, C +101, CI +102, CII +103, CIII +104, CIV +105, CV +106, CVI +107, CVII +108, CVIII +109, CIX +110, CX +111, CXI +112, CXII +113, CXIII +114, CXIV +115, CXV +116, CXVI +117, CXVII +118, CXVIII +119, CXIX +120, CXX +121, CXXI +122, CXXII +123, CXXIII +124, CXXIV +125, CXXV +126, CXXVI +127, CXXVII +128, CXXVIII +129, CXXIX +130, CXXX +131, CXXXI +132, CXXXII +133, CXXXIII +134, CXXXIV +135, CXXXV +136, CXXXVI +137, CXXXVII +138, CXXXVIII +139, CXXXIX +140, CXL +141, CXLI +142, CXLII +143, CXLIII +144, CXLIV +145, CXLV +146, CXLVI +147, CXLVII +148, CXLVIII +149, CXLIX +150, CL +151, CLI +152, CLII +153, CLIII +154, CLIV +155, CLV +156, CLVI +157, CLVII +158, CLVIII +159, CLIX +160, CLX +161, CLXI +162, CLXII +163, CLXIII +164, CLXIV +165, CLXV +166, CLXVI +167, CLXVII +168, CLXVIII +169, CLXIX +170, CLXX +171, CLXXI +172, CLXXII +173, CLXXIII +174, CLXXIV +175, CLXXV +176, CLXXVI +177, CLXXVII +178, CLXXVIII +179, CLXXIX +180, CLXXX +181, CLXXXI +182, CLXXXII +183, CLXXXIII +184, CLXXXIV +185, CLXXXV +186, CLXXXVI +187, CLXXXVII +188, CLXXXVIII +189, CLXXXIX +190, CXC +191, CXCI +192, CXCII +193, CXCIII +194, CXCIV +195, CXCV +196, CXCVI +197, CXCVII +198, CXCVIII +199, CXCIX +200, CC +201, CCI +202, CCII +203, CCIII +204, CCIV +205, CCV +206, CCVI +207, CCVII +208, CCVIII +209, CCIX +210, CCX +211, CCXI +212, CCXII +213, CCXIII +214, CCXIV +215, CCXV +216, CCXVI +217, CCXVII +218, CCXVIII +219, CCXIX +220, CCXX +221, CCXXI +222, CCXXII +223, CCXXIII +224, CCXXIV +225, CCXXV +226, CCXXVI +227, CCXXVII +228, CCXXVIII +229, CCXXIX +230, CCXXX +231, CCXXXI +232, CCXXXII +233, CCXXXIII +234, CCXXXIV +235, CCXXXV +236, CCXXXVI +237, CCXXXVII +238, CCXXXVIII +239, CCXXXIX +240, CCXL +241, CCXLI +242, CCXLII +243, CCXLIII +244, CCXLIV +245, CCXLV +246, CCXLVI +247, CCXLVII +248, CCXLVIII +249, CCXLIX +250, CCL +251, CCLI +252, CCLII +253, CCLIII +254, CCLIV +255, CCLV +256, CCLVI +257, CCLVII +258, CCLVIII +259, CCLIX +260, CCLX +261, CCLXI +262, CCLXII +263, CCLXIII +264, CCLXIV +265, CCLXV +266, CCLXVI +267, CCLXVII +268, CCLXVIII +269, CCLXIX +270, CCLXX +271, CCLXXI +272, CCLXXII +273, CCLXXIII +274, CCLXXIV +275, CCLXXV +276, CCLXXVI +277, CCLXXVII +278, CCLXXVIII +279, CCLXXIX +280, CCLXXX +281, CCLXXXI +282, CCLXXXII +283, CCLXXXIII +284, CCLXXXIV +285, CCLXXXV +286, CCLXXXVI +287, CCLXXXVII +288, CCLXXXVIII +289, CCLXXXIX +290, CCXC +291, CCXCI +292, CCXCII +293, CCXCIII +294, CCXCIV +295, CCXCV +296, CCXCVI +297, CCXCVII +298, CCXCVIII +299, CCXCIX +300, CCC +301, CCCI +302, CCCII +303, CCCIII +304, CCCIV +305, CCCV +306, CCCVI +307, CCCVII +308, CCCVIII +309, CCCIX +310, CCCX +311, CCCXI +312, CCCXII +313, CCCXIII +314, CCCXIV +315, CCCXV +316, CCCXVI +317, CCCXVII +318, CCCXVIII +319, CCCXIX +320, CCCXX +321, CCCXXI +322, CCCXXII +323, CCCXXIII +324, CCCXXIV +325, CCCXXV +326, CCCXXVI +327, CCCXXVII +328, CCCXXVIII +329, CCCXXIX +330, CCCXXX +331, CCCXXXI +332, CCCXXXII +333, CCCXXXIII +334, CCCXXXIV +335, CCCXXXV +336, CCCXXXVI +337, CCCXXXVII +338, CCCXXXVIII +339, CCCXXXIX +340, CCCXL +341, CCCXLI +342, CCCXLII +343, CCCXLIII +344, CCCXLIV +345, CCCXLV +346, CCCXLVI +347, CCCXLVII +348, CCCXLVIII +349, CCCXLIX +350, CCCL +351, CCCLI +352, CCCLII +353, CCCLIII +354, CCCLIV +355, CCCLV +356, CCCLVI +357, CCCLVII +358, CCCLVIII +359, CCCLIX +360, CCCLX +361, CCCLXI +362, CCCLXII +363, CCCLXIII +364, CCCLXIV +365, CCCLXV +366, CCCLXVI +367, CCCLXVII +368, CCCLXVIII +369, CCCLXIX +370, CCCLXX +371, CCCLXXI +372, CCCLXXII +373, CCCLXXIII +374, CCCLXXIV +375, CCCLXXV +376, CCCLXXVI +377, CCCLXXVII +378, CCCLXXVIII +379, CCCLXXIX +380, CCCLXXX +381, CCCLXXXI +382, CCCLXXXII +383, CCCLXXXIII +384, CCCLXXXIV +385, CCCLXXXV +386, CCCLXXXVI +387, CCCLXXXVII +388, CCCLXXXVIII +389, CCCLXXXIX +390, CCCXC +391, CCCXCI +392, CCCXCII +393, CCCXCIII +394, CCCXCIV +395, CCCXCV +396, CCCXCVI +397, CCCXCVII +398, CCCXCVIII +399, CCCXCIX +400, CD +401, CDI +402, CDII +403, CDIII +404, CDIV +405, CDV +406, CDVI +407, CDVII +408, CDVIII +409, CDIX +410, CDX +411, CDXI +412, CDXII +413, CDXIII +414, CDXIV +415, CDXV +416, CDXVI +417, CDXVII +418, CDXVIII +419, CDXIX +420, CDXX +421, CDXXI +422, CDXXII +423, CDXXIII +424, CDXXIV +425, CDXXV +426, CDXXVI +427, CDXXVII +428, CDXXVIII +429, CDXXIX +430, CDXXX +431, CDXXXI +432, CDXXXII +433, CDXXXIII +434, CDXXXIV +435, CDXXXV +436, CDXXXVI +437, CDXXXVII +438, CDXXXVIII +439, CDXXXIX +440, CDXL +441, CDXLI +442, CDXLII +443, CDXLIII +444, CDXLIV +445, CDXLV +446, CDXLVI +447, CDXLVII +448, CDXLVIII +449, CDXLIX +450, CDL +451, CDLI +452, CDLII +453, CDLIII +454, CDLIV +455, CDLV +456, CDLVI +457, CDLVII +458, CDLVIII +459, CDLIX +460, CDLX +461, CDLXI +462, CDLXII +463, CDLXIII +464, CDLXIV +465, CDLXV +466, CDLXVI +467, CDLXVII +468, CDLXVIII +469, CDLXIX +470, CDLXX +471, CDLXXI +472, CDLXXII +473, CDLXXIII +474, CDLXXIV +475, CDLXXV +476, CDLXXVI +477, CDLXXVII +478, CDLXXVIII +479, CDLXXIX +480, CDLXXX +481, CDLXXXI +482, CDLXXXII +483, CDLXXXIII +484, CDLXXXIV +485, CDLXXXV +486, CDLXXXVI +487, CDLXXXVII +488, CDLXXXVIII +489, CDLXXXIX +490, CDXC +491, CDXCI +492, CDXCII +493, CDXCIII +494, CDXCIV +495, CDXCV +496, CDXCVI +497, CDXCVII +498, CDXCVIII +499, CDXCIX +500, D +501, DI +502, DII +503, DIII +504, DIV +505, DV +506, DVI +507, DVII +508, DVIII +509, DIX +510, DX +511, DXI +512, DXII +513, DXIII +514, DXIV +515, DXV +516, DXVI +517, DXVII +518, DXVIII +519, DXIX +520, DXX +521, DXXI +522, DXXII +523, DXXIII +524, DXXIV +525, DXXV +526, DXXVI +527, DXXVII +528, DXXVIII +529, DXXIX +530, DXXX +531, DXXXI +532, DXXXII +533, DXXXIII +534, DXXXIV +535, DXXXV +536, DXXXVI +537, DXXXVII +538, DXXXVIII +539, DXXXIX +540, DXL +541, DXLI +542, DXLII +543, DXLIII +544, DXLIV +545, DXLV +546, DXLVI +547, DXLVII +548, DXLVIII +549, DXLIX +550, DL +551, DLI +552, DLII +553, DLIII +554, DLIV +555, DLV +556, DLVI +557, DLVII +558, DLVIII +559, DLIX +560, DLX +561, DLXI +562, DLXII +563, DLXIII +564, DLXIV +565, DLXV +566, DLXVI +567, DLXVII +568, DLXVIII +569, DLXIX +570, DLXX +571, DLXXI +572, DLXXII +573, DLXXIII +574, DLXXIV +575, DLXXV +576, DLXXVI +577, DLXXVII +578, DLXXVIII +579, DLXXIX +580, DLXXX +581, DLXXXI +582, DLXXXII +583, DLXXXIII +584, DLXXXIV +585, DLXXXV +586, DLXXXVI +587, DLXXXVII +588, DLXXXVIII +589, DLXXXIX +590, DXC +591, DXCI +592, DXCII +593, DXCIII +594, DXCIV +595, DXCV +596, DXCVI +597, DXCVII +598, DXCVIII +599, DXCIX +600, DC +601, DCI +602, DCII +603, DCIII +604, DCIV +605, DCV +606, DCVI +607, DCVII +608, DCVIII +609, DCIX +610, DCX +611, DCXI +612, DCXII +613, DCXIII +614, DCXIV +615, DCXV +616, DCXVI +617, DCXVII +618, DCXVIII +619, DCXIX +620, DCXX +621, DCXXI +622, DCXXII +623, DCXXIII +624, DCXXIV +625, DCXXV +626, DCXXVI +627, DCXXVII +628, DCXXVIII +629, DCXXIX +630, DCXXX +631, DCXXXI +632, DCXXXII +633, DCXXXIII +634, DCXXXIV +635, DCXXXV +636, DCXXXVI +637, DCXXXVII +638, DCXXXVIII +639, DCXXXIX +640, DCXL +641, DCXLI +642, DCXLII +643, DCXLIII +644, DCXLIV +645, DCXLV +646, DCXLVI +647, DCXLVII +648, DCXLVIII +649, DCXLIX +650, DCL +651, DCLI +652, DCLII +653, DCLIII +654, DCLIV +655, DCLV +656, DCLVI +657, DCLVII +658, DCLVIII +659, DCLIX +660, DCLX +661, DCLXI +662, DCLXII +663, DCLXIII +664, DCLXIV +665, DCLXV +666, DCLXVI +667, DCLXVII +668, DCLXVIII +669, DCLXIX +670, DCLXX +671, DCLXXI +672, DCLXXII +673, DCLXXIII +674, DCLXXIV +675, DCLXXV +676, DCLXXVI +677, DCLXXVII +678, DCLXXVIII +679, DCLXXIX +680, DCLXXX +681, DCLXXXI +682, DCLXXXII +683, DCLXXXIII +684, DCLXXXIV +685, DCLXXXV +686, DCLXXXVI +687, DCLXXXVII +688, DCLXXXVIII +689, DCLXXXIX +690, DCXC +691, DCXCI +692, DCXCII +693, DCXCIII +694, DCXCIV +695, DCXCV +696, DCXCVI +697, DCXCVII +698, DCXCVIII +699, DCXCIX +700, DCC +701, DCCI +702, DCCII +703, DCCIII +704, DCCIV +705, DCCV +706, DCCVI +707, DCCVII +708, DCCVIII +709, DCCIX +710, DCCX +711, DCCXI +712, DCCXII +713, DCCXIII +714, DCCXIV +715, DCCXV +716, DCCXVI +717, DCCXVII +718, DCCXVIII +719, DCCXIX +720, DCCXX +721, DCCXXI +722, DCCXXII +723, DCCXXIII +724, DCCXXIV +725, DCCXXV +726, DCCXXVI +727, DCCXXVII +728, DCCXXVIII +729, DCCXXIX +730, DCCXXX +731, DCCXXXI +732, DCCXXXII +733, DCCXXXIII +734, DCCXXXIV +735, DCCXXXV +736, DCCXXXVI +737, DCCXXXVII +738, DCCXXXVIII +739, DCCXXXIX +740, DCCXL +741, DCCXLI +742, DCCXLII +743, DCCXLIII +744, DCCXLIV +745, DCCXLV +746, DCCXLVI +747, DCCXLVII +748, DCCXLVIII +749, DCCXLIX +750, DCCL +751, DCCLI +752, DCCLII +753, DCCLIII +754, DCCLIV +755, DCCLV +756, DCCLVI +757, DCCLVII +758, DCCLVIII +759, DCCLIX +760, DCCLX +761, DCCLXI +762, DCCLXII +763, DCCLXIII +764, DCCLXIV +765, DCCLXV +766, DCCLXVI +767, DCCLXVII +768, DCCLXVIII +769, DCCLXIX +770, DCCLXX +771, DCCLXXI +772, DCCLXXII +773, DCCLXXIII +774, DCCLXXIV +775, DCCLXXV +776, DCCLXXVI +777, DCCLXXVII +778, DCCLXXVIII +779, DCCLXXIX +780, DCCLXXX +781, DCCLXXXI +782, DCCLXXXII +783, DCCLXXXIII +784, DCCLXXXIV +785, DCCLXXXV +786, DCCLXXXVI +787, DCCLXXXVII +788, DCCLXXXVIII +789, DCCLXXXIX +790, DCCXC +791, DCCXCI +792, DCCXCII +793, DCCXCIII +794, DCCXCIV +795, DCCXCV +796, DCCXCVI +797, DCCXCVII +798, DCCXCVIII +799, DCCXCIX +800, DCCC +801, DCCCI +802, DCCCII +803, DCCCIII +804, DCCCIV +805, DCCCV +806, DCCCVI +807, DCCCVII +808, DCCCVIII +809, DCCCIX +810, DCCCX +811, DCCCXI +812, DCCCXII +813, DCCCXIII +814, DCCCXIV +815, DCCCXV +816, DCCCXVI +817, DCCCXVII +818, DCCCXVIII +819, DCCCXIX +820, DCCCXX +821, DCCCXXI +822, DCCCXXII +823, DCCCXXIII +824, DCCCXXIV +825, DCCCXXV +826, DCCCXXVI +827, DCCCXXVII +828, DCCCXXVIII +829, DCCCXXIX +830, DCCCXXX +831, DCCCXXXI +832, DCCCXXXII +833, DCCCXXXIII +834, DCCCXXXIV +835, DCCCXXXV +836, DCCCXXXVI +837, DCCCXXXVII +838, DCCCXXXVIII +839, DCCCXXXIX +840, DCCCXL +841, DCCCXLI +842, DCCCXLII +843, DCCCXLIII +844, DCCCXLIV +845, DCCCXLV +846, DCCCXLVI +847, DCCCXLVII +848, DCCCXLVIII +849, DCCCXLIX +850, DCCCL +851, DCCCLI +852, DCCCLII +853, DCCCLIII +854, DCCCLIV +855, DCCCLV +856, DCCCLVI +857, DCCCLVII +858, DCCCLVIII +859, DCCCLIX +860, DCCCLX +861, DCCCLXI +862, DCCCLXII +863, DCCCLXIII +864, DCCCLXIV +865, DCCCLXV +866, DCCCLXVI +867, DCCCLXVII +868, DCCCLXVIII +869, DCCCLXIX +870, DCCCLXX +871, DCCCLXXI +872, DCCCLXXII +873, DCCCLXXIII +874, DCCCLXXIV +875, DCCCLXXV +876, DCCCLXXVI +877, DCCCLXXVII +878, DCCCLXXVIII +879, DCCCLXXIX +880, DCCCLXXX +881, DCCCLXXXI +882, DCCCLXXXII +883, DCCCLXXXIII +884, DCCCLXXXIV +885, DCCCLXXXV +886, DCCCLXXXVI +887, DCCCLXXXVII +888, DCCCLXXXVIII +889, DCCCLXXXIX +890, DCCCXC +891, DCCCXCI +892, DCCCXCII +893, DCCCXCIII +894, DCCCXCIV +895, DCCCXCV +896, DCCCXCVI +897, DCCCXCVII +898, DCCCXCVIII +899, DCCCXCIX +900, CM +901, CMI +902, CMII +903, CMIII +904, CMIV +905, CMV +906, CMVI +907, CMVII +908, CMVIII +909, CMIX +910, CMX +911, CMXI +912, CMXII +913, CMXIII +914, CMXIV +915, CMXV +916, CMXVI +917, CMXVII +918, CMXVIII +919, CMXIX +920, CMXX +921, CMXXI +922, CMXXII +923, CMXXIII +924, CMXXIV +925, CMXXV +926, CMXXVI +927, CMXXVII +928, CMXXVIII +929, CMXXIX +930, CMXXX +931, CMXXXI +932, CMXXXII +933, CMXXXIII +934, CMXXXIV +935, CMXXXV +936, CMXXXVI +937, CMXXXVII +938, CMXXXVIII +939, CMXXXIX +940, CMXL +941, CMXLI +942, CMXLII +943, CMXLIII +944, CMXLIV +945, CMXLV +946, CMXLVI +947, CMXLVII +948, CMXLVIII +949, CMXLIX +950, CML +951, CMLI +952, CMLII +953, CMLIII +954, CMLIV +955, CMLV +956, CMLVI +957, CMLVII +958, CMLVIII +959, CMLIX +960, CMLX +961, CMLXI +962, CMLXII +963, CMLXIII +964, CMLXIV +965, CMLXV +966, CMLXVI +967, CMLXVII +968, CMLXVIII +969, CMLXIX +970, CMLXX +971, CMLXXI +972, CMLXXII +973, CMLXXIII +974, CMLXXIV +975, CMLXXV +976, CMLXXVI +977, CMLXXVII +978, CMLXXVIII +979, CMLXXIX +980, CMLXXX +981, CMLXXXI +982, CMLXXXII +983, CMLXXXIII +984, CMLXXXIV +985, CMLXXXV +986, CMLXXXVI +987, CMLXXXVII +988, CMLXXXVIII +989, CMLXXXIX +990, CMXC +991, CMXCI +992, CMXCII +993, CMXCIII +994, CMXCIV +995, CMXCV +996, CMXCVI +997, CMXCVII +998, CMXCVIII +999, CMXCIX +1000, M +1001, MI +1002, MII +1003, MIII +1004, MIV +1005, MV +1006, MVI +1007, MVII +1008, MVIII +1009, MIX +1010, MX +1011, MXI +1012, MXII +1013, MXIII +1014, MXIV +1015, MXV +1016, MXVI +1017, MXVII +1018, MXVIII +1019, MXIX +1020, MXX +1021, MXXI +1022, MXXII +1023, MXXIII +1024, MXXIV +1025, MXXV +1026, MXXVI +1027, MXXVII +1028, MXXVIII +1029, MXXIX +1030, MXXX +1031, MXXXI +1032, MXXXII +1033, MXXXIII +1034, MXXXIV +1035, MXXXV +1036, MXXXVI +1037, MXXXVII +1038, MXXXVIII +1039, MXXXIX +1040, MXL +1041, MXLI +1042, MXLII +1043, MXLIII +1044, MXLIV +1045, MXLV +1046, MXLVI +1047, MXLVII +1048, MXLVIII +1049, MXLIX +1050, ML +1051, MLI +1052, MLII +1053, MLIII +1054, MLIV +1055, MLV +1056, MLVI +1057, MLVII +1058, MLVIII +1059, MLIX +1060, MLX +1061, MLXI +1062, MLXII +1063, MLXIII +1064, MLXIV +1065, MLXV +1066, MLXVI +1067, MLXVII +1068, MLXVIII +1069, MLXIX +1070, MLXX +1071, MLXXI +1072, MLXXII +1073, MLXXIII +1074, MLXXIV +1075, MLXXV +1076, MLXXVI +1077, MLXXVII +1078, MLXXVIII +1079, MLXXIX +1080, MLXXX +1081, MLXXXI +1082, MLXXXII +1083, MLXXXIII +1084, MLXXXIV +1085, MLXXXV +1086, MLXXXVI +1087, MLXXXVII +1088, MLXXXVIII +1089, MLXXXIX +1090, MXC +1091, MXCI +1092, MXCII +1093, MXCIII +1094, MXCIV +1095, MXCV +1096, MXCVI +1097, MXCVII +1098, MXCVIII +1099, MXCIX +1100, MC +1101, MCI +1102, MCII +1103, MCIII +1104, MCIV +1105, MCV +1106, MCVI +1107, MCVII +1108, MCVIII +1109, MCIX +1110, MCX +1111, MCXI +1112, MCXII +1113, MCXIII +1114, MCXIV +1115, MCXV +1116, MCXVI +1117, MCXVII +1118, MCXVIII +1119, MCXIX +1120, MCXX +1121, MCXXI +1122, MCXXII +1123, MCXXIII +1124, MCXXIV +1125, MCXXV +1126, MCXXVI +1127, MCXXVII +1128, MCXXVIII +1129, MCXXIX +1130, MCXXX +1131, MCXXXI +1132, MCXXXII +1133, MCXXXIII +1134, MCXXXIV +1135, MCXXXV +1136, MCXXXVI +1137, MCXXXVII +1138, MCXXXVIII +1139, MCXXXIX +1140, MCXL +1141, MCXLI +1142, MCXLII +1143, MCXLIII +1144, MCXLIV +1145, MCXLV +1146, MCXLVI +1147, MCXLVII +1148, MCXLVIII +1149, MCXLIX +1150, MCL +1151, MCLI +1152, MCLII +1153, MCLIII +1154, MCLIV +1155, MCLV +1156, MCLVI +1157, MCLVII +1158, MCLVIII +1159, MCLIX +1160, MCLX +1161, MCLXI +1162, MCLXII +1163, MCLXIII +1164, MCLXIV +1165, MCLXV +1166, MCLXVI +1167, MCLXVII +1168, MCLXVIII +1169, MCLXIX +1170, MCLXX +1171, MCLXXI +1172, MCLXXII +1173, MCLXXIII +1174, MCLXXIV +1175, MCLXXV +1176, MCLXXVI +1177, MCLXXVII +1178, MCLXXVIII +1179, MCLXXIX +1180, MCLXXX +1181, MCLXXXI +1182, MCLXXXII +1183, MCLXXXIII +1184, MCLXXXIV +1185, MCLXXXV +1186, MCLXXXVI +1187, MCLXXXVII +1188, MCLXXXVIII +1189, MCLXXXIX +1190, MCXC +1191, MCXCI +1192, MCXCII +1193, MCXCIII +1194, MCXCIV +1195, MCXCV +1196, MCXCVI +1197, MCXCVII +1198, MCXCVIII +1199, MCXCIX +1200, MCC +1201, MCCI +1202, MCCII +1203, MCCIII +1204, MCCIV +1205, MCCV +1206, MCCVI +1207, MCCVII +1208, MCCVIII +1209, MCCIX +1210, MCCX +1211, MCCXI +1212, MCCXII +1213, MCCXIII +1214, MCCXIV +1215, MCCXV +1216, MCCXVI +1217, MCCXVII +1218, MCCXVIII +1219, MCCXIX +1220, MCCXX +1221, MCCXXI +1222, MCCXXII +1223, MCCXXIII +1224, MCCXXIV +1225, MCCXXV +1226, MCCXXVI +1227, MCCXXVII +1228, MCCXXVIII +1229, MCCXXIX +1230, MCCXXX +1231, MCCXXXI +1232, MCCXXXII +1233, MCCXXXIII +1234, MCCXXXIV +1235, MCCXXXV +1236, MCCXXXVI +1237, MCCXXXVII +1238, MCCXXXVIII +1239, MCCXXXIX +1240, MCCXL +1241, MCCXLI +1242, MCCXLII +1243, MCCXLIII +1244, MCCXLIV +1245, MCCXLV +1246, MCCXLVI +1247, MCCXLVII +1248, MCCXLVIII +1249, MCCXLIX +1250, MCCL +1251, MCCLI +1252, MCCLII +1253, MCCLIII +1254, MCCLIV +1255, MCCLV +1256, MCCLVI +1257, MCCLVII +1258, MCCLVIII +1259, MCCLIX +1260, MCCLX +1261, MCCLXI +1262, MCCLXII +1263, MCCLXIII +1264, MCCLXIV +1265, MCCLXV +1266, MCCLXVI +1267, MCCLXVII +1268, MCCLXVIII +1269, MCCLXIX +1270, MCCLXX +1271, MCCLXXI +1272, MCCLXXII +1273, MCCLXXIII +1274, MCCLXXIV +1275, MCCLXXV +1276, MCCLXXVI +1277, MCCLXXVII +1278, MCCLXXVIII +1279, MCCLXXIX +1280, MCCLXXX +1281, MCCLXXXI +1282, MCCLXXXII +1283, MCCLXXXIII +1284, MCCLXXXIV +1285, MCCLXXXV +1286, MCCLXXXVI +1287, MCCLXXXVII +1288, MCCLXXXVIII +1289, MCCLXXXIX +1290, MCCXC +1291, MCCXCI +1292, MCCXCII +1293, MCCXCIII +1294, MCCXCIV +1295, MCCXCV +1296, MCCXCVI +1297, MCCXCVII +1298, MCCXCVIII +1299, MCCXCIX +1300, MCCC +1301, MCCCI +1302, MCCCII +1303, MCCCIII +1304, MCCCIV +1305, MCCCV +1306, MCCCVI +1307, MCCCVII +1308, MCCCVIII +1309, MCCCIX +1310, MCCCX +1311, MCCCXI +1312, MCCCXII +1313, MCCCXIII +1314, MCCCXIV +1315, MCCCXV +1316, MCCCXVI +1317, MCCCXVII +1318, MCCCXVIII +1319, MCCCXIX +1320, MCCCXX +1321, MCCCXXI +1322, MCCCXXII +1323, MCCCXXIII +1324, MCCCXXIV +1325, MCCCXXV +1326, MCCCXXVI +1327, MCCCXXVII +1328, MCCCXXVIII +1329, MCCCXXIX +1330, MCCCXXX +1331, MCCCXXXI +1332, MCCCXXXII +1333, MCCCXXXIII +1334, MCCCXXXIV +1335, MCCCXXXV +1336, MCCCXXXVI +1337, MCCCXXXVII +1338, MCCCXXXVIII +1339, MCCCXXXIX +1340, MCCCXL +1341, MCCCXLI +1342, MCCCXLII +1343, MCCCXLIII +1344, MCCCXLIV +1345, MCCCXLV +1346, MCCCXLVI +1347, MCCCXLVII +1348, MCCCXLVIII +1349, MCCCXLIX +1350, MCCCL +1351, MCCCLI +1352, MCCCLII +1353, MCCCLIII +1354, MCCCLIV +1355, MCCCLV +1356, MCCCLVI +1357, MCCCLVII +1358, MCCCLVIII +1359, MCCCLIX +1360, MCCCLX +1361, MCCCLXI +1362, MCCCLXII +1363, MCCCLXIII +1364, MCCCLXIV +1365, MCCCLXV +1366, MCCCLXVI +1367, MCCCLXVII +1368, MCCCLXVIII +1369, MCCCLXIX +1370, MCCCLXX +1371, MCCCLXXI +1372, MCCCLXXII +1373, MCCCLXXIII +1374, MCCCLXXIV +1375, MCCCLXXV +1376, MCCCLXXVI +1377, MCCCLXXVII +1378, MCCCLXXVIII +1379, MCCCLXXIX +1380, MCCCLXXX +1381, MCCCLXXXI +1382, MCCCLXXXII +1383, MCCCLXXXIII +1384, MCCCLXXXIV +1385, MCCCLXXXV +1386, MCCCLXXXVI +1387, MCCCLXXXVII +1388, MCCCLXXXVIII +1389, MCCCLXXXIX +1390, MCCCXC +1391, MCCCXCI +1392, MCCCXCII +1393, MCCCXCIII +1394, MCCCXCIV +1395, MCCCXCV +1396, MCCCXCVI +1397, MCCCXCVII +1398, MCCCXCVIII +1399, MCCCXCIX +1400, MCD +1401, MCDI +1402, MCDII +1403, MCDIII +1404, MCDIV +1405, MCDV +1406, MCDVI +1407, MCDVII +1408, MCDVIII +1409, MCDIX +1410, MCDX +1411, MCDXI +1412, MCDXII +1413, MCDXIII +1414, MCDXIV +1415, MCDXV +1416, MCDXVI +1417, MCDXVII +1418, MCDXVIII +1419, MCDXIX +1420, MCDXX +1421, MCDXXI +1422, MCDXXII +1423, MCDXXIII +1424, MCDXXIV +1425, MCDXXV +1426, MCDXXVI +1427, MCDXXVII +1428, MCDXXVIII +1429, MCDXXIX +1430, MCDXXX +1431, MCDXXXI +1432, MCDXXXII +1433, MCDXXXIII +1434, MCDXXXIV +1435, MCDXXXV +1436, MCDXXXVI +1437, MCDXXXVII +1438, MCDXXXVIII +1439, MCDXXXIX +1440, MCDXL +1441, MCDXLI +1442, MCDXLII +1443, MCDXLIII +1444, MCDXLIV +1445, MCDXLV +1446, MCDXLVI +1447, MCDXLVII +1448, MCDXLVIII +1449, MCDXLIX +1450, MCDL +1451, MCDLI +1452, MCDLII +1453, MCDLIII +1454, MCDLIV +1455, MCDLV +1456, MCDLVI +1457, MCDLVII +1458, MCDLVIII +1459, MCDLIX +1460, MCDLX +1461, MCDLXI +1462, MCDLXII +1463, MCDLXIII +1464, MCDLXIV +1465, MCDLXV +1466, MCDLXVI +1467, MCDLXVII +1468, MCDLXVIII +1469, MCDLXIX +1470, MCDLXX +1471, MCDLXXI +1472, MCDLXXII +1473, MCDLXXIII +1474, MCDLXXIV +1475, MCDLXXV +1476, MCDLXXVI +1477, MCDLXXVII +1478, MCDLXXVIII +1479, MCDLXXIX +1480, MCDLXXX +1481, MCDLXXXI +1482, MCDLXXXII +1483, MCDLXXXIII +1484, MCDLXXXIV +1485, MCDLXXXV +1486, MCDLXXXVI +1487, MCDLXXXVII +1488, MCDLXXXVIII +1489, MCDLXXXIX +1490, MCDXC +1491, MCDXCI +1492, MCDXCII +1493, MCDXCIII +1494, MCDXCIV +1495, MCDXCV +1496, MCDXCVI +1497, MCDXCVII +1498, MCDXCVIII +1499, MCDXCIX +1500, MD +1501, MDI +1502, MDII +1503, MDIII +1504, MDIV +1505, MDV +1506, MDVI +1507, MDVII +1508, MDVIII +1509, MDIX +1510, MDX +1511, MDXI +1512, MDXII +1513, MDXIII +1514, MDXIV +1515, MDXV +1516, MDXVI +1517, MDXVII +1518, MDXVIII +1519, MDXIX +1520, MDXX +1521, MDXXI +1522, MDXXII +1523, MDXXIII +1524, MDXXIV +1525, MDXXV +1526, MDXXVI +1527, MDXXVII +1528, MDXXVIII +1529, MDXXIX +1530, MDXXX +1531, MDXXXI +1532, MDXXXII +1533, MDXXXIII +1534, MDXXXIV +1535, MDXXXV +1536, MDXXXVI +1537, MDXXXVII +1538, MDXXXVIII +1539, MDXXXIX +1540, MDXL +1541, MDXLI +1542, MDXLII +1543, MDXLIII +1544, MDXLIV +1545, MDXLV +1546, MDXLVI +1547, MDXLVII +1548, MDXLVIII +1549, MDXLIX +1550, MDL +1551, MDLI +1552, MDLII +1553, MDLIII +1554, MDLIV +1555, MDLV +1556, MDLVI +1557, MDLVII +1558, MDLVIII +1559, MDLIX +1560, MDLX +1561, MDLXI +1562, MDLXII +1563, MDLXIII +1564, MDLXIV +1565, MDLXV +1566, MDLXVI +1567, MDLXVII +1568, MDLXVIII +1569, MDLXIX +1570, MDLXX +1571, MDLXXI +1572, MDLXXII +1573, MDLXXIII +1574, MDLXXIV +1575, MDLXXV +1576, MDLXXVI +1577, MDLXXVII +1578, MDLXXVIII +1579, MDLXXIX +1580, MDLXXX +1581, MDLXXXI +1582, MDLXXXII +1583, MDLXXXIII +1584, MDLXXXIV +1585, MDLXXXV +1586, MDLXXXVI +1587, MDLXXXVII +1588, MDLXXXVIII +1589, MDLXXXIX +1590, MDXC +1591, MDXCI +1592, MDXCII +1593, MDXCIII +1594, MDXCIV +1595, MDXCV +1596, MDXCVI +1597, MDXCVII +1598, MDXCVIII +1599, MDXCIX +1600, MDC +1601, MDCI +1602, MDCII +1603, MDCIII +1604, MDCIV +1605, MDCV +1606, MDCVI +1607, MDCVII +1608, MDCVIII +1609, MDCIX +1610, MDCX +1611, MDCXI +1612, MDCXII +1613, MDCXIII +1614, MDCXIV +1615, MDCXV +1616, MDCXVI +1617, MDCXVII +1618, MDCXVIII +1619, MDCXIX +1620, MDCXX +1621, MDCXXI +1622, MDCXXII +1623, MDCXXIII +1624, MDCXXIV +1625, MDCXXV +1626, MDCXXVI +1627, MDCXXVII +1628, MDCXXVIII +1629, MDCXXIX +1630, MDCXXX +1631, MDCXXXI +1632, MDCXXXII +1633, MDCXXXIII +1634, MDCXXXIV +1635, MDCXXXV +1636, MDCXXXVI +1637, MDCXXXVII +1638, MDCXXXVIII +1639, MDCXXXIX +1640, MDCXL +1641, MDCXLI +1642, MDCXLII +1643, MDCXLIII +1644, MDCXLIV +1645, MDCXLV +1646, MDCXLVI +1647, MDCXLVII +1648, MDCXLVIII +1649, MDCXLIX +1650, MDCL +1651, MDCLI +1652, MDCLII +1653, MDCLIII +1654, MDCLIV +1655, MDCLV +1656, MDCLVI +1657, MDCLVII +1658, MDCLVIII +1659, MDCLIX +1660, MDCLX +1661, MDCLXI +1662, MDCLXII +1663, MDCLXIII +1664, MDCLXIV +1665, MDCLXV +1666, MDCLXVI +1667, MDCLXVII +1668, MDCLXVIII +1669, MDCLXIX +1670, MDCLXX +1671, MDCLXXI +1672, MDCLXXII +1673, MDCLXXIII +1674, MDCLXXIV +1675, MDCLXXV +1676, MDCLXXVI +1677, MDCLXXVII +1678, MDCLXXVIII +1679, MDCLXXIX +1680, MDCLXXX +1681, MDCLXXXI +1682, MDCLXXXII +1683, MDCLXXXIII +1684, MDCLXXXIV +1685, MDCLXXXV +1686, MDCLXXXVI +1687, MDCLXXXVII +1688, MDCLXXXVIII +1689, MDCLXXXIX +1690, MDCXC +1691, MDCXCI +1692, MDCXCII +1693, MDCXCIII +1694, MDCXCIV +1695, MDCXCV +1696, MDCXCVI +1697, MDCXCVII +1698, MDCXCVIII +1699, MDCXCIX +1700, MDCC +1701, MDCCI +1702, MDCCII +1703, MDCCIII +1704, MDCCIV +1705, MDCCV +1706, MDCCVI +1707, MDCCVII +1708, MDCCVIII +1709, MDCCIX +1710, MDCCX +1711, MDCCXI +1712, MDCCXII +1713, MDCCXIII +1714, MDCCXIV +1715, MDCCXV +1716, MDCCXVI +1717, MDCCXVII +1718, MDCCXVIII +1719, MDCCXIX +1720, MDCCXX +1721, MDCCXXI +1722, MDCCXXII +1723, MDCCXXIII +1724, MDCCXXIV +1725, MDCCXXV +1726, MDCCXXVI +1727, MDCCXXVII +1728, MDCCXXVIII +1729, MDCCXXIX +1730, MDCCXXX +1731, MDCCXXXI +1732, MDCCXXXII +1733, MDCCXXXIII +1734, MDCCXXXIV +1735, MDCCXXXV +1736, MDCCXXXVI +1737, MDCCXXXVII +1738, MDCCXXXVIII +1739, MDCCXXXIX +1740, MDCCXL +1741, MDCCXLI +1742, MDCCXLII +1743, MDCCXLIII +1744, MDCCXLIV +1745, MDCCXLV +1746, MDCCXLVI +1747, MDCCXLVII +1748, MDCCXLVIII +1749, MDCCXLIX +1750, MDCCL +1751, MDCCLI +1752, MDCCLII +1753, MDCCLIII +1754, MDCCLIV +1755, MDCCLV +1756, MDCCLVI +1757, MDCCLVII +1758, MDCCLVIII +1759, MDCCLIX +1760, MDCCLX +1761, MDCCLXI +1762, MDCCLXII +1763, MDCCLXIII +1764, MDCCLXIV +1765, MDCCLXV +1766, MDCCLXVI +1767, MDCCLXVII +1768, MDCCLXVIII +1769, MDCCLXIX +1770, MDCCLXX +1771, MDCCLXXI +1772, MDCCLXXII +1773, MDCCLXXIII +1774, MDCCLXXIV +1775, MDCCLXXV +1776, MDCCLXXVI +1777, MDCCLXXVII +1778, MDCCLXXVIII +1779, MDCCLXXIX +1780, MDCCLXXX +1781, MDCCLXXXI +1782, MDCCLXXXII +1783, MDCCLXXXIII +1784, MDCCLXXXIV +1785, MDCCLXXXV +1786, MDCCLXXXVI +1787, MDCCLXXXVII +1788, MDCCLXXXVIII +1789, MDCCLXXXIX +1790, MDCCXC +1791, MDCCXCI +1792, MDCCXCII +1793, MDCCXCIII +1794, MDCCXCIV +1795, MDCCXCV +1796, MDCCXCVI +1797, MDCCXCVII +1798, MDCCXCVIII +1799, MDCCXCIX +1800, MDCCC +1801, MDCCCI +1802, MDCCCII +1803, MDCCCIII +1804, MDCCCIV +1805, MDCCCV +1806, MDCCCVI +1807, MDCCCVII +1808, MDCCCVIII +1809, MDCCCIX +1810, MDCCCX +1811, MDCCCXI +1812, MDCCCXII +1813, MDCCCXIII +1814, MDCCCXIV +1815, MDCCCXV +1816, MDCCCXVI +1817, MDCCCXVII +1818, MDCCCXVIII +1819, MDCCCXIX +1820, MDCCCXX +1821, MDCCCXXI +1822, MDCCCXXII +1823, MDCCCXXIII +1824, MDCCCXXIV +1825, MDCCCXXV +1826, MDCCCXXVI +1827, MDCCCXXVII +1828, MDCCCXXVIII +1829, MDCCCXXIX +1830, MDCCCXXX +1831, MDCCCXXXI +1832, MDCCCXXXII +1833, MDCCCXXXIII +1834, MDCCCXXXIV +1835, MDCCCXXXV +1836, MDCCCXXXVI +1837, MDCCCXXXVII +1838, MDCCCXXXVIII +1839, MDCCCXXXIX +1840, MDCCCXL +1841, MDCCCXLI +1842, MDCCCXLII +1843, MDCCCXLIII +1844, MDCCCXLIV +1845, MDCCCXLV +1846, MDCCCXLVI +1847, MDCCCXLVII +1848, MDCCCXLVIII +1849, MDCCCXLIX +1850, MDCCCL +1851, MDCCCLI +1852, MDCCCLII +1853, MDCCCLIII +1854, MDCCCLIV +1855, MDCCCLV +1856, MDCCCLVI +1857, MDCCCLVII +1858, MDCCCLVIII +1859, MDCCCLIX +1860, MDCCCLX +1861, MDCCCLXI +1862, MDCCCLXII +1863, MDCCCLXIII +1864, MDCCCLXIV +1865, MDCCCLXV +1866, MDCCCLXVI +1867, MDCCCLXVII +1868, MDCCCLXVIII +1869, MDCCCLXIX +1870, MDCCCLXX +1871, MDCCCLXXI +1872, MDCCCLXXII +1873, MDCCCLXXIII +1874, MDCCCLXXIV +1875, MDCCCLXXV +1876, MDCCCLXXVI +1877, MDCCCLXXVII +1878, MDCCCLXXVIII +1879, MDCCCLXXIX +1880, MDCCCLXXX +1881, MDCCCLXXXI +1882, MDCCCLXXXII +1883, MDCCCLXXXIII +1884, MDCCCLXXXIV +1885, MDCCCLXXXV +1886, MDCCCLXXXVI +1887, MDCCCLXXXVII +1888, MDCCCLXXXVIII +1889, MDCCCLXXXIX +1890, MDCCCXC +1891, MDCCCXCI +1892, MDCCCXCII +1893, MDCCCXCIII +1894, MDCCCXCIV +1895, MDCCCXCV +1896, MDCCCXCVI +1897, MDCCCXCVII +1898, MDCCCXCVIII +1899, MDCCCXCIX +1900, MCM +1901, MCMI +1902, MCMII +1903, MCMIII +1904, MCMIV +1905, MCMV +1906, MCMVI +1907, MCMVII +1908, MCMVIII +1909, MCMIX +1910, MCMX +1911, MCMXI +1912, MCMXII +1913, MCMXIII +1914, MCMXIV +1915, MCMXV +1916, MCMXVI +1917, MCMXVII +1918, MCMXVIII +1919, MCMXIX +1920, MCMXX +1921, MCMXXI +1922, MCMXXII +1923, MCMXXIII +1924, MCMXXIV +1925, MCMXXV +1926, MCMXXVI +1927, MCMXXVII +1928, MCMXXVIII +1929, MCMXXIX +1930, MCMXXX +1931, MCMXXXI +1932, MCMXXXII +1933, MCMXXXIII +1934, MCMXXXIV +1935, MCMXXXV +1936, MCMXXXVI +1937, MCMXXXVII +1938, MCMXXXVIII +1939, MCMXXXIX +1940, MCMXL +1941, MCMXLI +1942, MCMXLII +1943, MCMXLIII +1944, MCMXLIV +1945, MCMXLV +1946, MCMXLVI +1947, MCMXLVII +1948, MCMXLVIII +1949, MCMXLIX +1950, MCML +1951, MCMLI +1952, MCMLII +1953, MCMLIII +1954, MCMLIV +1955, MCMLV +1956, MCMLVI +1957, MCMLVII +1958, MCMLVIII +1959, MCMLIX +1960, MCMLX +1961, MCMLXI +1962, MCMLXII +1963, MCMLXIII +1964, MCMLXIV +1965, MCMLXV +1966, MCMLXVI +1967, MCMLXVII +1968, MCMLXVIII +1969, MCMLXIX +1970, MCMLXX +1971, MCMLXXI +1972, MCMLXXII +1973, MCMLXXIII +1974, MCMLXXIV +1975, MCMLXXV +1976, MCMLXXVI +1977, MCMLXXVII +1978, MCMLXXVIII +1979, MCMLXXIX +1980, MCMLXXX +1981, MCMLXXXI +1982, MCMLXXXII +1983, MCMLXXXIII +1984, MCMLXXXIV +1985, MCMLXXXV +1986, MCMLXXXVI +1987, MCMLXXXVII +1988, MCMLXXXVIII +1989, MCMLXXXIX +1990, MCMXC +1991, MCMXCI +1992, MCMXCII +1993, MCMXCIII +1994, MCMXCIV +1995, MCMXCV +1996, MCMXCVI +1997, MCMXCVII +1998, MCMXCVIII +1999, MCMXCIX +2000, MM +2001, MMI +2002, MMII +2003, MMIII +2004, MMIV +2005, MMV +2006, MMVI +2007, MMVII +2008, MMVIII +2009, MMIX +2010, MMX +2011, MMXI +2012, MMXII +2013, MMXIII +2014, MMXIV +2015, MMXV +2016, MMXVI +2017, MMXVII +2018, MMXVIII +2019, MMXIX +2020, MMXX +2021, MMXXI +2022, MMXXII +2023, MMXXIII +2024, MMXXIV +2025, MMXXV +2026, MMXXVI +2027, MMXXVII +2028, MMXXVIII +2029, MMXXIX +2030, MMXXX +2031, MMXXXI +2032, MMXXXII +2033, MMXXXIII +2034, MMXXXIV +2035, MMXXXV +2036, MMXXXVI +2037, MMXXXVII +2038, MMXXXVIII +2039, MMXXXIX +2040, MMXL +2041, MMXLI +2042, MMXLII +2043, MMXLIII +2044, MMXLIV +2045, MMXLV +2046, MMXLVI +2047, MMXLVII +2048, MMXLVIII +2049, MMXLIX +2050, MML +2051, MMLI +2052, MMLII +2053, MMLIII +2054, MMLIV +2055, MMLV +2056, MMLVI +2057, MMLVII +2058, MMLVIII +2059, MMLIX +2060, MMLX +2061, MMLXI +2062, MMLXII +2063, MMLXIII +2064, MMLXIV +2065, MMLXV +2066, MMLXVI +2067, MMLXVII +2068, MMLXVIII +2069, MMLXIX +2070, MMLXX +2071, MMLXXI +2072, MMLXXII +2073, MMLXXIII +2074, MMLXXIV +2075, MMLXXV +2076, MMLXXVI +2077, MMLXXVII +2078, MMLXXVIII +2079, MMLXXIX +2080, MMLXXX +2081, MMLXXXI +2082, MMLXXXII +2083, MMLXXXIII +2084, MMLXXXIV +2085, MMLXXXV +2086, MMLXXXVI +2087, MMLXXXVII +2088, MMLXXXVIII +2089, MMLXXXIX +2090, MMXC +2091, MMXCI +2092, MMXCII +2093, MMXCIII +2094, MMXCIV +2095, MMXCV +2096, MMXCVI +2097, MMXCVII +2098, MMXCVIII +2099, MMXCIX +2100, MMC +2101, MMCI +2102, MMCII +2103, MMCIII +2104, MMCIV +2105, MMCV +2106, MMCVI +2107, MMCVII +2108, MMCVIII +2109, MMCIX +2110, MMCX +2111, MMCXI +2112, MMCXII +2113, MMCXIII +2114, MMCXIV +2115, MMCXV +2116, MMCXVI +2117, MMCXVII +2118, MMCXVIII +2119, MMCXIX +2120, MMCXX +2121, MMCXXI +2122, MMCXXII +2123, MMCXXIII +2124, MMCXXIV +2125, MMCXXV +2126, MMCXXVI +2127, MMCXXVII +2128, MMCXXVIII +2129, MMCXXIX +2130, MMCXXX +2131, MMCXXXI +2132, MMCXXXII +2133, MMCXXXIII +2134, MMCXXXIV +2135, MMCXXXV +2136, MMCXXXVI +2137, MMCXXXVII +2138, MMCXXXVIII +2139, MMCXXXIX +2140, MMCXL +2141, MMCXLI +2142, MMCXLII +2143, MMCXLIII +2144, MMCXLIV +2145, MMCXLV +2146, MMCXLVI +2147, MMCXLVII +2148, MMCXLVIII +2149, MMCXLIX +2150, MMCL +2151, MMCLI +2152, MMCLII +2153, MMCLIII +2154, MMCLIV +2155, MMCLV +2156, MMCLVI +2157, MMCLVII +2158, MMCLVIII +2159, MMCLIX +2160, MMCLX +2161, MMCLXI +2162, MMCLXII +2163, MMCLXIII +2164, MMCLXIV +2165, MMCLXV +2166, MMCLXVI +2167, MMCLXVII +2168, MMCLXVIII +2169, MMCLXIX +2170, MMCLXX +2171, MMCLXXI +2172, MMCLXXII +2173, MMCLXXIII +2174, MMCLXXIV +2175, MMCLXXV +2176, MMCLXXVI +2177, MMCLXXVII +2178, MMCLXXVIII +2179, MMCLXXIX +2180, MMCLXXX +2181, MMCLXXXI +2182, MMCLXXXII +2183, MMCLXXXIII +2184, MMCLXXXIV +2185, MMCLXXXV +2186, MMCLXXXVI +2187, MMCLXXXVII +2188, MMCLXXXVIII +2189, MMCLXXXIX +2190, MMCXC +2191, MMCXCI +2192, MMCXCII +2193, MMCXCIII +2194, MMCXCIV +2195, MMCXCV +2196, MMCXCVI +2197, MMCXCVII +2198, MMCXCVIII +2199, MMCXCIX +2200, MMCC +2201, MMCCI +2202, MMCCII +2203, MMCCIII +2204, MMCCIV +2205, MMCCV +2206, MMCCVI +2207, MMCCVII +2208, MMCCVIII +2209, MMCCIX +2210, MMCCX +2211, MMCCXI +2212, MMCCXII +2213, MMCCXIII +2214, MMCCXIV +2215, MMCCXV +2216, MMCCXVI +2217, MMCCXVII +2218, MMCCXVIII +2219, MMCCXIX +2220, MMCCXX +2221, MMCCXXI +2222, MMCCXXII +2223, MMCCXXIII +2224, MMCCXXIV +2225, MMCCXXV +2226, MMCCXXVI +2227, MMCCXXVII +2228, MMCCXXVIII +2229, MMCCXXIX +2230, MMCCXXX +2231, MMCCXXXI +2232, MMCCXXXII +2233, MMCCXXXIII +2234, MMCCXXXIV +2235, MMCCXXXV +2236, MMCCXXXVI +2237, MMCCXXXVII +2238, MMCCXXXVIII +2239, MMCCXXXIX +2240, MMCCXL +2241, MMCCXLI +2242, MMCCXLII +2243, MMCCXLIII +2244, MMCCXLIV +2245, MMCCXLV +2246, MMCCXLVI +2247, MMCCXLVII +2248, MMCCXLVIII +2249, MMCCXLIX +2250, MMCCL +2251, MMCCLI +2252, MMCCLII +2253, MMCCLIII +2254, MMCCLIV +2255, MMCCLV +2256, MMCCLVI +2257, MMCCLVII +2258, MMCCLVIII +2259, MMCCLIX +2260, MMCCLX +2261, MMCCLXI +2262, MMCCLXII +2263, MMCCLXIII +2264, MMCCLXIV +2265, MMCCLXV +2266, MMCCLXVI +2267, MMCCLXVII +2268, MMCCLXVIII +2269, MMCCLXIX +2270, MMCCLXX +2271, MMCCLXXI +2272, MMCCLXXII +2273, MMCCLXXIII +2274, MMCCLXXIV +2275, MMCCLXXV +2276, MMCCLXXVI +2277, MMCCLXXVII +2278, MMCCLXXVIII +2279, MMCCLXXIX +2280, MMCCLXXX +2281, MMCCLXXXI +2282, MMCCLXXXII +2283, MMCCLXXXIII +2284, MMCCLXXXIV +2285, MMCCLXXXV +2286, MMCCLXXXVI +2287, MMCCLXXXVII +2288, MMCCLXXXVIII +2289, MMCCLXXXIX +2290, MMCCXC +2291, MMCCXCI +2292, MMCCXCII +2293, MMCCXCIII +2294, MMCCXCIV +2295, MMCCXCV +2296, MMCCXCVI +2297, MMCCXCVII +2298, MMCCXCVIII +2299, MMCCXCIX +2300, MMCCC +2301, MMCCCI +2302, MMCCCII +2303, MMCCCIII +2304, MMCCCIV +2305, MMCCCV +2306, MMCCCVI +2307, MMCCCVII +2308, MMCCCVIII +2309, MMCCCIX +2310, MMCCCX +2311, MMCCCXI +2312, MMCCCXII +2313, MMCCCXIII +2314, MMCCCXIV +2315, MMCCCXV +2316, MMCCCXVI +2317, MMCCCXVII +2318, MMCCCXVIII +2319, MMCCCXIX +2320, MMCCCXX +2321, MMCCCXXI +2322, MMCCCXXII +2323, MMCCCXXIII +2324, MMCCCXXIV +2325, MMCCCXXV +2326, MMCCCXXVI +2327, MMCCCXXVII +2328, MMCCCXXVIII +2329, MMCCCXXIX +2330, MMCCCXXX +2331, MMCCCXXXI +2332, MMCCCXXXII +2333, MMCCCXXXIII +2334, MMCCCXXXIV +2335, MMCCCXXXV +2336, MMCCCXXXVI +2337, MMCCCXXXVII +2338, MMCCCXXXVIII +2339, MMCCCXXXIX +2340, MMCCCXL +2341, MMCCCXLI +2342, MMCCCXLII +2343, MMCCCXLIII +2344, MMCCCXLIV +2345, MMCCCXLV +2346, MMCCCXLVI +2347, MMCCCXLVII +2348, MMCCCXLVIII +2349, MMCCCXLIX +2350, MMCCCL +2351, MMCCCLI +2352, MMCCCLII +2353, MMCCCLIII +2354, MMCCCLIV +2355, MMCCCLV +2356, MMCCCLVI +2357, MMCCCLVII +2358, MMCCCLVIII +2359, MMCCCLIX +2360, MMCCCLX +2361, MMCCCLXI +2362, MMCCCLXII +2363, MMCCCLXIII +2364, MMCCCLXIV +2365, MMCCCLXV +2366, MMCCCLXVI +2367, MMCCCLXVII +2368, MMCCCLXVIII +2369, MMCCCLXIX +2370, MMCCCLXX +2371, MMCCCLXXI +2372, MMCCCLXXII +2373, MMCCCLXXIII +2374, MMCCCLXXIV +2375, MMCCCLXXV +2376, MMCCCLXXVI +2377, MMCCCLXXVII +2378, MMCCCLXXVIII +2379, MMCCCLXXIX +2380, MMCCCLXXX +2381, MMCCCLXXXI +2382, MMCCCLXXXII +2383, MMCCCLXXXIII +2384, MMCCCLXXXIV +2385, MMCCCLXXXV +2386, MMCCCLXXXVI +2387, MMCCCLXXXVII +2388, MMCCCLXXXVIII +2389, MMCCCLXXXIX +2390, MMCCCXC +2391, MMCCCXCI +2392, MMCCCXCII +2393, MMCCCXCIII +2394, MMCCCXCIV +2395, MMCCCXCV +2396, MMCCCXCVI +2397, MMCCCXCVII +2398, MMCCCXCVIII +2399, MMCCCXCIX +2400, MMCD +2401, MMCDI +2402, MMCDII +2403, MMCDIII +2404, MMCDIV +2405, MMCDV +2406, MMCDVI +2407, MMCDVII +2408, MMCDVIII +2409, MMCDIX +2410, MMCDX +2411, MMCDXI +2412, MMCDXII +2413, MMCDXIII +2414, MMCDXIV +2415, MMCDXV +2416, MMCDXVI +2417, MMCDXVII +2418, MMCDXVIII +2419, MMCDXIX +2420, MMCDXX +2421, MMCDXXI +2422, MMCDXXII +2423, MMCDXXIII +2424, MMCDXXIV +2425, MMCDXXV +2426, MMCDXXVI +2427, MMCDXXVII +2428, MMCDXXVIII +2429, MMCDXXIX +2430, MMCDXXX +2431, MMCDXXXI +2432, MMCDXXXII +2433, MMCDXXXIII +2434, MMCDXXXIV +2435, MMCDXXXV +2436, MMCDXXXVI +2437, MMCDXXXVII +2438, MMCDXXXVIII +2439, MMCDXXXIX +2440, MMCDXL +2441, MMCDXLI +2442, MMCDXLII +2443, MMCDXLIII +2444, MMCDXLIV +2445, MMCDXLV +2446, MMCDXLVI +2447, MMCDXLVII +2448, MMCDXLVIII +2449, MMCDXLIX +2450, MMCDL +2451, MMCDLI +2452, MMCDLII +2453, MMCDLIII +2454, MMCDLIV +2455, MMCDLV +2456, MMCDLVI +2457, MMCDLVII +2458, MMCDLVIII +2459, MMCDLIX +2460, MMCDLX +2461, MMCDLXI +2462, MMCDLXII +2463, MMCDLXIII +2464, MMCDLXIV +2465, MMCDLXV +2466, MMCDLXVI +2467, MMCDLXVII +2468, MMCDLXVIII +2469, MMCDLXIX +2470, MMCDLXX +2471, MMCDLXXI +2472, MMCDLXXII +2473, MMCDLXXIII +2474, MMCDLXXIV +2475, MMCDLXXV +2476, MMCDLXXVI +2477, MMCDLXXVII +2478, MMCDLXXVIII +2479, MMCDLXXIX +2480, MMCDLXXX +2481, MMCDLXXXI +2482, MMCDLXXXII +2483, MMCDLXXXIII +2484, MMCDLXXXIV +2485, MMCDLXXXV +2486, MMCDLXXXVI +2487, MMCDLXXXVII +2488, MMCDLXXXVIII +2489, MMCDLXXXIX +2490, MMCDXC +2491, MMCDXCI +2492, MMCDXCII +2493, MMCDXCIII +2494, MMCDXCIV +2495, MMCDXCV +2496, MMCDXCVI +2497, MMCDXCVII +2498, MMCDXCVIII +2499, MMCDXCIX +2500, MMD +2501, MMDI +2502, MMDII +2503, MMDIII +2504, MMDIV +2505, MMDV +2506, MMDVI +2507, MMDVII +2508, MMDVIII +2509, MMDIX +2510, MMDX +2511, MMDXI +2512, MMDXII +2513, MMDXIII +2514, MMDXIV +2515, MMDXV +2516, MMDXVI +2517, MMDXVII +2518, MMDXVIII +2519, MMDXIX +2520, MMDXX +2521, MMDXXI +2522, MMDXXII +2523, MMDXXIII +2524, MMDXXIV +2525, MMDXXV +2526, MMDXXVI +2527, MMDXXVII +2528, MMDXXVIII +2529, MMDXXIX +2530, MMDXXX +2531, MMDXXXI +2532, MMDXXXII +2533, MMDXXXIII +2534, MMDXXXIV +2535, MMDXXXV +2536, MMDXXXVI +2537, MMDXXXVII +2538, MMDXXXVIII +2539, MMDXXXIX +2540, MMDXL +2541, MMDXLI +2542, MMDXLII +2543, MMDXLIII +2544, MMDXLIV +2545, MMDXLV +2546, MMDXLVI +2547, MMDXLVII +2548, MMDXLVIII +2549, MMDXLIX +2550, MMDL +2551, MMDLI +2552, MMDLII +2553, MMDLIII +2554, MMDLIV +2555, MMDLV +2556, MMDLVI +2557, MMDLVII +2558, MMDLVIII +2559, MMDLIX +2560, MMDLX +2561, MMDLXI +2562, MMDLXII +2563, MMDLXIII +2564, MMDLXIV +2565, MMDLXV +2566, MMDLXVI +2567, MMDLXVII +2568, MMDLXVIII +2569, MMDLXIX +2570, MMDLXX +2571, MMDLXXI +2572, MMDLXXII +2573, MMDLXXIII +2574, MMDLXXIV +2575, MMDLXXV +2576, MMDLXXVI +2577, MMDLXXVII +2578, MMDLXXVIII +2579, MMDLXXIX +2580, MMDLXXX +2581, MMDLXXXI +2582, MMDLXXXII +2583, MMDLXXXIII +2584, MMDLXXXIV +2585, MMDLXXXV +2586, MMDLXXXVI +2587, MMDLXXXVII +2588, MMDLXXXVIII +2589, MMDLXXXIX +2590, MMDXC +2591, MMDXCI +2592, MMDXCII +2593, MMDXCIII +2594, MMDXCIV +2595, MMDXCV +2596, MMDXCVI +2597, MMDXCVII +2598, MMDXCVIII +2599, MMDXCIX +2600, MMDC +2601, MMDCI +2602, MMDCII +2603, MMDCIII +2604, MMDCIV +2605, MMDCV +2606, MMDCVI +2607, MMDCVII +2608, MMDCVIII +2609, MMDCIX +2610, MMDCX +2611, MMDCXI +2612, MMDCXII +2613, MMDCXIII +2614, MMDCXIV +2615, MMDCXV +2616, MMDCXVI +2617, MMDCXVII +2618, MMDCXVIII +2619, MMDCXIX +2620, MMDCXX +2621, MMDCXXI +2622, MMDCXXII +2623, MMDCXXIII +2624, MMDCXXIV +2625, MMDCXXV +2626, MMDCXXVI +2627, MMDCXXVII +2628, MMDCXXVIII +2629, MMDCXXIX +2630, MMDCXXX +2631, MMDCXXXI +2632, MMDCXXXII +2633, MMDCXXXIII +2634, MMDCXXXIV +2635, MMDCXXXV +2636, MMDCXXXVI +2637, MMDCXXXVII +2638, MMDCXXXVIII +2639, MMDCXXXIX +2640, MMDCXL +2641, MMDCXLI +2642, MMDCXLII +2643, MMDCXLIII +2644, MMDCXLIV +2645, MMDCXLV +2646, MMDCXLVI +2647, MMDCXLVII +2648, MMDCXLVIII +2649, MMDCXLIX +2650, MMDCL +2651, MMDCLI +2652, MMDCLII +2653, MMDCLIII +2654, MMDCLIV +2655, MMDCLV +2656, MMDCLVI +2657, MMDCLVII +2658, MMDCLVIII +2659, MMDCLIX +2660, MMDCLX +2661, MMDCLXI +2662, MMDCLXII +2663, MMDCLXIII +2664, MMDCLXIV +2665, MMDCLXV +2666, MMDCLXVI +2667, MMDCLXVII +2668, MMDCLXVIII +2669, MMDCLXIX +2670, MMDCLXX +2671, MMDCLXXI +2672, MMDCLXXII +2673, MMDCLXXIII +2674, MMDCLXXIV +2675, MMDCLXXV +2676, MMDCLXXVI +2677, MMDCLXXVII +2678, MMDCLXXVIII +2679, MMDCLXXIX +2680, MMDCLXXX +2681, MMDCLXXXI +2682, MMDCLXXXII +2683, MMDCLXXXIII +2684, MMDCLXXXIV +2685, MMDCLXXXV +2686, MMDCLXXXVI +2687, MMDCLXXXVII +2688, MMDCLXXXVIII +2689, MMDCLXXXIX +2690, MMDCXC +2691, MMDCXCI +2692, MMDCXCII +2693, MMDCXCIII +2694, MMDCXCIV +2695, MMDCXCV +2696, MMDCXCVI +2697, MMDCXCVII +2698, MMDCXCVIII +2699, MMDCXCIX +2700, MMDCC +2701, MMDCCI +2702, MMDCCII +2703, MMDCCIII +2704, MMDCCIV +2705, MMDCCV +2706, MMDCCVI +2707, MMDCCVII +2708, MMDCCVIII +2709, MMDCCIX +2710, MMDCCX +2711, MMDCCXI +2712, MMDCCXII +2713, MMDCCXIII +2714, MMDCCXIV +2715, MMDCCXV +2716, MMDCCXVI +2717, MMDCCXVII +2718, MMDCCXVIII +2719, MMDCCXIX +2720, MMDCCXX +2721, MMDCCXXI +2722, MMDCCXXII +2723, MMDCCXXIII +2724, MMDCCXXIV +2725, MMDCCXXV +2726, MMDCCXXVI +2727, MMDCCXXVII +2728, MMDCCXXVIII +2729, MMDCCXXIX +2730, MMDCCXXX +2731, MMDCCXXXI +2732, MMDCCXXXII +2733, MMDCCXXXIII +2734, MMDCCXXXIV +2735, MMDCCXXXV +2736, MMDCCXXXVI +2737, MMDCCXXXVII +2738, MMDCCXXXVIII +2739, MMDCCXXXIX +2740, MMDCCXL +2741, MMDCCXLI +2742, MMDCCXLII +2743, MMDCCXLIII +2744, MMDCCXLIV +2745, MMDCCXLV +2746, MMDCCXLVI +2747, MMDCCXLVII +2748, MMDCCXLVIII +2749, MMDCCXLIX +2750, MMDCCL +2751, MMDCCLI +2752, MMDCCLII +2753, MMDCCLIII +2754, MMDCCLIV +2755, MMDCCLV +2756, MMDCCLVI +2757, MMDCCLVII +2758, MMDCCLVIII +2759, MMDCCLIX +2760, MMDCCLX +2761, MMDCCLXI +2762, MMDCCLXII +2763, MMDCCLXIII +2764, MMDCCLXIV +2765, MMDCCLXV +2766, MMDCCLXVI +2767, MMDCCLXVII +2768, MMDCCLXVIII +2769, MMDCCLXIX +2770, MMDCCLXX +2771, MMDCCLXXI +2772, MMDCCLXXII +2773, MMDCCLXXIII +2774, MMDCCLXXIV +2775, MMDCCLXXV +2776, MMDCCLXXVI +2777, MMDCCLXXVII +2778, MMDCCLXXVIII +2779, MMDCCLXXIX +2780, MMDCCLXXX +2781, MMDCCLXXXI +2782, MMDCCLXXXII +2783, MMDCCLXXXIII +2784, MMDCCLXXXIV +2785, MMDCCLXXXV +2786, MMDCCLXXXVI +2787, MMDCCLXXXVII +2788, MMDCCLXXXVIII +2789, MMDCCLXXXIX +2790, MMDCCXC +2791, MMDCCXCI +2792, MMDCCXCII +2793, MMDCCXCIII +2794, MMDCCXCIV +2795, MMDCCXCV +2796, MMDCCXCVI +2797, MMDCCXCVII +2798, MMDCCXCVIII +2799, MMDCCXCIX +2800, MMDCCC +2801, MMDCCCI +2802, MMDCCCII +2803, MMDCCCIII +2804, MMDCCCIV +2805, MMDCCCV +2806, MMDCCCVI +2807, MMDCCCVII +2808, MMDCCCVIII +2809, MMDCCCIX +2810, MMDCCCX +2811, MMDCCCXI +2812, MMDCCCXII +2813, MMDCCCXIII +2814, MMDCCCXIV +2815, MMDCCCXV +2816, MMDCCCXVI +2817, MMDCCCXVII +2818, MMDCCCXVIII +2819, MMDCCCXIX +2820, MMDCCCXX +2821, MMDCCCXXI +2822, MMDCCCXXII +2823, MMDCCCXXIII +2824, MMDCCCXXIV +2825, MMDCCCXXV +2826, MMDCCCXXVI +2827, MMDCCCXXVII +2828, MMDCCCXXVIII +2829, MMDCCCXXIX +2830, MMDCCCXXX +2831, MMDCCCXXXI +2832, MMDCCCXXXII +2833, MMDCCCXXXIII +2834, MMDCCCXXXIV +2835, MMDCCCXXXV +2836, MMDCCCXXXVI +2837, MMDCCCXXXVII +2838, MMDCCCXXXVIII +2839, MMDCCCXXXIX +2840, MMDCCCXL +2841, MMDCCCXLI +2842, MMDCCCXLII +2843, MMDCCCXLIII +2844, MMDCCCXLIV +2845, MMDCCCXLV +2846, MMDCCCXLVI +2847, MMDCCCXLVII +2848, MMDCCCXLVIII +2849, MMDCCCXLIX +2850, MMDCCCL +2851, MMDCCCLI +2852, MMDCCCLII +2853, MMDCCCLIII +2854, MMDCCCLIV +2855, MMDCCCLV +2856, MMDCCCLVI +2857, MMDCCCLVII +2858, MMDCCCLVIII +2859, MMDCCCLIX +2860, MMDCCCLX +2861, MMDCCCLXI +2862, MMDCCCLXII +2863, MMDCCCLXIII +2864, MMDCCCLXIV +2865, MMDCCCLXV +2866, MMDCCCLXVI +2867, MMDCCCLXVII +2868, MMDCCCLXVIII +2869, MMDCCCLXIX +2870, MMDCCCLXX +2871, MMDCCCLXXI +2872, MMDCCCLXXII +2873, MMDCCCLXXIII +2874, MMDCCCLXXIV +2875, MMDCCCLXXV +2876, MMDCCCLXXVI +2877, MMDCCCLXXVII +2878, MMDCCCLXXVIII +2879, MMDCCCLXXIX +2880, MMDCCCLXXX +2881, MMDCCCLXXXI +2882, MMDCCCLXXXII +2883, MMDCCCLXXXIII +2884, MMDCCCLXXXIV +2885, MMDCCCLXXXV +2886, MMDCCCLXXXVI +2887, MMDCCCLXXXVII +2888, MMDCCCLXXXVIII +2889, MMDCCCLXXXIX +2890, MMDCCCXC +2891, MMDCCCXCI +2892, MMDCCCXCII +2893, MMDCCCXCIII +2894, MMDCCCXCIV +2895, MMDCCCXCV +2896, MMDCCCXCVI +2897, MMDCCCXCVII +2898, MMDCCCXCVIII +2899, MMDCCCXCIX +2900, MMCM +2901, MMCMI +2902, MMCMII +2903, MMCMIII +2904, MMCMIV +2905, MMCMV +2906, MMCMVI +2907, MMCMVII +2908, MMCMVIII +2909, MMCMIX +2910, MMCMX +2911, MMCMXI +2912, MMCMXII +2913, MMCMXIII +2914, MMCMXIV +2915, MMCMXV +2916, MMCMXVI +2917, MMCMXVII +2918, MMCMXVIII +2919, MMCMXIX +2920, MMCMXX +2921, MMCMXXI +2922, MMCMXXII +2923, MMCMXXIII +2924, MMCMXXIV +2925, MMCMXXV +2926, MMCMXXVI +2927, MMCMXXVII +2928, MMCMXXVIII +2929, MMCMXXIX +2930, MMCMXXX +2931, MMCMXXXI +2932, MMCMXXXII +2933, MMCMXXXIII +2934, MMCMXXXIV +2935, MMCMXXXV +2936, MMCMXXXVI +2937, MMCMXXXVII +2938, MMCMXXXVIII +2939, MMCMXXXIX +2940, MMCMXL +2941, MMCMXLI +2942, MMCMXLII +2943, MMCMXLIII +2944, MMCMXLIV +2945, MMCMXLV +2946, MMCMXLVI +2947, MMCMXLVII +2948, MMCMXLVIII +2949, MMCMXLIX +2950, MMCML +2951, MMCMLI +2952, MMCMLII +2953, MMCMLIII +2954, MMCMLIV +2955, MMCMLV +2956, MMCMLVI +2957, MMCMLVII +2958, MMCMLVIII +2959, MMCMLIX +2960, MMCMLX +2961, MMCMLXI +2962, MMCMLXII +2963, MMCMLXIII +2964, MMCMLXIV +2965, MMCMLXV +2966, MMCMLXVI +2967, MMCMLXVII +2968, MMCMLXVIII +2969, MMCMLXIX +2970, MMCMLXX +2971, MMCMLXXI +2972, MMCMLXXII +2973, MMCMLXXIII +2974, MMCMLXXIV +2975, MMCMLXXV +2976, MMCMLXXVI +2977, MMCMLXXVII +2978, MMCMLXXVIII +2979, MMCMLXXIX +2980, MMCMLXXX +2981, MMCMLXXXI +2982, MMCMLXXXII +2983, MMCMLXXXIII +2984, MMCMLXXXIV +2985, MMCMLXXXV +2986, MMCMLXXXVI +2987, MMCMLXXXVII +2988, MMCMLXXXVIII +2989, MMCMLXXXIX +2990, MMCMXC +2991, MMCMXCI +2992, MMCMXCII +2993, MMCMXCIII +2994, MMCMXCIV +2995, MMCMXCV +2996, MMCMXCVI +2997, MMCMXCVII +2998, MMCMXCVIII +2999, MMCMXCIX +3000, MMM +3001, MMMI +3002, MMMII +3003, MMMIII +3004, MMMIV +3005, MMMV +3006, MMMVI +3007, MMMVII +3008, MMMVIII +3009, MMMIX +3010, MMMX +3011, MMMXI +3012, MMMXII +3013, MMMXIII +3014, MMMXIV +3015, MMMXV +3016, MMMXVI +3017, MMMXVII +3018, MMMXVIII +3019, MMMXIX +3020, MMMXX +3021, MMMXXI +3022, MMMXXII +3023, MMMXXIII +3024, MMMXXIV +3025, MMMXXV +3026, MMMXXVI +3027, MMMXXVII +3028, MMMXXVIII +3029, MMMXXIX +3030, MMMXXX +3031, MMMXXXI +3032, MMMXXXII +3033, MMMXXXIII +3034, MMMXXXIV +3035, MMMXXXV +3036, MMMXXXVI +3037, MMMXXXVII +3038, MMMXXXVIII +3039, MMMXXXIX +3040, MMMXL +3041, MMMXLI +3042, MMMXLII +3043, MMMXLIII +3044, MMMXLIV +3045, MMMXLV +3046, MMMXLVI +3047, MMMXLVII +3048, MMMXLVIII +3049, MMMXLIX +3050, MMML +3051, MMMLI +3052, MMMLII +3053, MMMLIII +3054, MMMLIV +3055, MMMLV +3056, MMMLVI +3057, MMMLVII +3058, MMMLVIII +3059, MMMLIX +3060, MMMLX +3061, MMMLXI +3062, MMMLXII +3063, MMMLXIII +3064, MMMLXIV +3065, MMMLXV +3066, MMMLXVI +3067, MMMLXVII +3068, MMMLXVIII +3069, MMMLXIX +3070, MMMLXX +3071, MMMLXXI +3072, MMMLXXII +3073, MMMLXXIII +3074, MMMLXXIV +3075, MMMLXXV +3076, MMMLXXVI +3077, MMMLXXVII +3078, MMMLXXVIII +3079, MMMLXXIX +3080, MMMLXXX +3081, MMMLXXXI +3082, MMMLXXXII +3083, MMMLXXXIII +3084, MMMLXXXIV +3085, MMMLXXXV +3086, MMMLXXXVI +3087, MMMLXXXVII +3088, MMMLXXXVIII +3089, MMMLXXXIX +3090, MMMXC +3091, MMMXCI +3092, MMMXCII +3093, MMMXCIII +3094, MMMXCIV +3095, MMMXCV +3096, MMMXCVI +3097, MMMXCVII +3098, MMMXCVIII +3099, MMMXCIX +3100, MMMC +3101, MMMCI +3102, MMMCII +3103, MMMCIII +3104, MMMCIV +3105, MMMCV +3106, MMMCVI +3107, MMMCVII +3108, MMMCVIII +3109, MMMCIX +3110, MMMCX +3111, MMMCXI +3112, MMMCXII +3113, MMMCXIII +3114, MMMCXIV +3115, MMMCXV +3116, MMMCXVI +3117, MMMCXVII +3118, MMMCXVIII +3119, MMMCXIX +3120, MMMCXX +3121, MMMCXXI +3122, MMMCXXII +3123, MMMCXXIII +3124, MMMCXXIV +3125, MMMCXXV +3126, MMMCXXVI +3127, MMMCXXVII +3128, MMMCXXVIII +3129, MMMCXXIX +3130, MMMCXXX +3131, MMMCXXXI +3132, MMMCXXXII +3133, MMMCXXXIII +3134, MMMCXXXIV +3135, MMMCXXXV +3136, MMMCXXXVI +3137, MMMCXXXVII +3138, MMMCXXXVIII +3139, MMMCXXXIX +3140, MMMCXL +3141, MMMCXLI +3142, MMMCXLII +3143, MMMCXLIII +3144, MMMCXLIV +3145, MMMCXLV +3146, MMMCXLVI +3147, MMMCXLVII +3148, MMMCXLVIII +3149, MMMCXLIX +3150, MMMCL +3151, MMMCLI +3152, MMMCLII +3153, MMMCLIII +3154, MMMCLIV +3155, MMMCLV +3156, MMMCLVI +3157, MMMCLVII +3158, MMMCLVIII +3159, MMMCLIX +3160, MMMCLX +3161, MMMCLXI +3162, MMMCLXII +3163, MMMCLXIII +3164, MMMCLXIV +3165, MMMCLXV +3166, MMMCLXVI +3167, MMMCLXVII +3168, MMMCLXVIII +3169, MMMCLXIX +3170, MMMCLXX +3171, MMMCLXXI +3172, MMMCLXXII +3173, MMMCLXXIII +3174, MMMCLXXIV +3175, MMMCLXXV +3176, MMMCLXXVI +3177, MMMCLXXVII +3178, MMMCLXXVIII +3179, MMMCLXXIX +3180, MMMCLXXX +3181, MMMCLXXXI +3182, MMMCLXXXII +3183, MMMCLXXXIII +3184, MMMCLXXXIV +3185, MMMCLXXXV +3186, MMMCLXXXVI +3187, MMMCLXXXVII +3188, MMMCLXXXVIII +3189, MMMCLXXXIX +3190, MMMCXC +3191, MMMCXCI +3192, MMMCXCII +3193, MMMCXCIII +3194, MMMCXCIV +3195, MMMCXCV +3196, MMMCXCVI +3197, MMMCXCVII +3198, MMMCXCVIII +3199, MMMCXCIX +3200, MMMCC +3201, MMMCCI +3202, MMMCCII +3203, MMMCCIII +3204, MMMCCIV +3205, MMMCCV +3206, MMMCCVI +3207, MMMCCVII +3208, MMMCCVIII +3209, MMMCCIX +3210, MMMCCX +3211, MMMCCXI +3212, MMMCCXII +3213, MMMCCXIII +3214, MMMCCXIV +3215, MMMCCXV +3216, MMMCCXVI +3217, MMMCCXVII +3218, MMMCCXVIII +3219, MMMCCXIX +3220, MMMCCXX +3221, MMMCCXXI +3222, MMMCCXXII +3223, MMMCCXXIII +3224, MMMCCXXIV +3225, MMMCCXXV +3226, MMMCCXXVI +3227, MMMCCXXVII +3228, MMMCCXXVIII +3229, MMMCCXXIX +3230, MMMCCXXX +3231, MMMCCXXXI +3232, MMMCCXXXII +3233, MMMCCXXXIII +3234, MMMCCXXXIV +3235, MMMCCXXXV +3236, MMMCCXXXVI +3237, MMMCCXXXVII +3238, MMMCCXXXVIII +3239, MMMCCXXXIX +3240, MMMCCXL +3241, MMMCCXLI +3242, MMMCCXLII +3243, MMMCCXLIII +3244, MMMCCXLIV +3245, MMMCCXLV +3246, MMMCCXLVI +3247, MMMCCXLVII +3248, MMMCCXLVIII +3249, MMMCCXLIX +3250, MMMCCL +3251, MMMCCLI +3252, MMMCCLII +3253, MMMCCLIII +3254, MMMCCLIV +3255, MMMCCLV +3256, MMMCCLVI +3257, MMMCCLVII +3258, MMMCCLVIII +3259, MMMCCLIX +3260, MMMCCLX +3261, MMMCCLXI +3262, MMMCCLXII +3263, MMMCCLXIII +3264, MMMCCLXIV +3265, MMMCCLXV +3266, MMMCCLXVI +3267, MMMCCLXVII +3268, MMMCCLXVIII +3269, MMMCCLXIX +3270, MMMCCLXX +3271, MMMCCLXXI +3272, MMMCCLXXII +3273, MMMCCLXXIII +3274, MMMCCLXXIV +3275, MMMCCLXXV +3276, MMMCCLXXVI +3277, MMMCCLXXVII +3278, MMMCCLXXVIII +3279, MMMCCLXXIX +3280, MMMCCLXXX +3281, MMMCCLXXXI +3282, MMMCCLXXXII +3283, MMMCCLXXXIII +3284, MMMCCLXXXIV +3285, MMMCCLXXXV +3286, MMMCCLXXXVI +3287, MMMCCLXXXVII +3288, MMMCCLXXXVIII +3289, MMMCCLXXXIX +3290, MMMCCXC +3291, MMMCCXCI +3292, MMMCCXCII +3293, MMMCCXCIII +3294, MMMCCXCIV +3295, MMMCCXCV +3296, MMMCCXCVI +3297, MMMCCXCVII +3298, MMMCCXCVIII +3299, MMMCCXCIX +3300, MMMCCC +3301, MMMCCCI +3302, MMMCCCII +3303, MMMCCCIII +3304, MMMCCCIV +3305, MMMCCCV +3306, MMMCCCVI +3307, MMMCCCVII +3308, MMMCCCVIII +3309, MMMCCCIX +3310, MMMCCCX +3311, MMMCCCXI +3312, MMMCCCXII +3313, MMMCCCXIII +3314, MMMCCCXIV +3315, MMMCCCXV +3316, MMMCCCXVI +3317, MMMCCCXVII +3318, MMMCCCXVIII +3319, MMMCCCXIX +3320, MMMCCCXX +3321, MMMCCCXXI +3322, MMMCCCXXII +3323, MMMCCCXXIII +3324, MMMCCCXXIV +3325, MMMCCCXXV +3326, MMMCCCXXVI +3327, MMMCCCXXVII +3328, MMMCCCXXVIII +3329, MMMCCCXXIX +3330, MMMCCCXXX +3331, MMMCCCXXXI +3332, MMMCCCXXXII +3333, MMMCCCXXXIII +3334, MMMCCCXXXIV +3335, MMMCCCXXXV +3336, MMMCCCXXXVI +3337, MMMCCCXXXVII +3338, MMMCCCXXXVIII +3339, MMMCCCXXXIX +3340, MMMCCCXL +3341, MMMCCCXLI +3342, MMMCCCXLII +3343, MMMCCCXLIII +3344, MMMCCCXLIV +3345, MMMCCCXLV +3346, MMMCCCXLVI +3347, MMMCCCXLVII +3348, MMMCCCXLVIII +3349, MMMCCCXLIX +3350, MMMCCCL +3351, MMMCCCLI +3352, MMMCCCLII +3353, MMMCCCLIII +3354, MMMCCCLIV +3355, MMMCCCLV +3356, MMMCCCLVI +3357, MMMCCCLVII +3358, MMMCCCLVIII +3359, MMMCCCLIX +3360, MMMCCCLX +3361, MMMCCCLXI +3362, MMMCCCLXII +3363, MMMCCCLXIII +3364, MMMCCCLXIV +3365, MMMCCCLXV +3366, MMMCCCLXVI +3367, MMMCCCLXVII +3368, MMMCCCLXVIII +3369, MMMCCCLXIX +3370, MMMCCCLXX +3371, MMMCCCLXXI +3372, MMMCCCLXXII +3373, MMMCCCLXXIII +3374, MMMCCCLXXIV +3375, MMMCCCLXXV +3376, MMMCCCLXXVI +3377, MMMCCCLXXVII +3378, MMMCCCLXXVIII +3379, MMMCCCLXXIX +3380, MMMCCCLXXX +3381, MMMCCCLXXXI +3382, MMMCCCLXXXII +3383, MMMCCCLXXXIII +3384, MMMCCCLXXXIV +3385, MMMCCCLXXXV +3386, MMMCCCLXXXVI +3387, MMMCCCLXXXVII +3388, MMMCCCLXXXVIII +3389, MMMCCCLXXXIX +3390, MMMCCCXC +3391, MMMCCCXCI +3392, MMMCCCXCII +3393, MMMCCCXCIII +3394, MMMCCCXCIV +3395, MMMCCCXCV +3396, MMMCCCXCVI +3397, MMMCCCXCVII +3398, MMMCCCXCVIII +3399, MMMCCCXCIX +3400, MMMCD +3401, MMMCDI +3402, MMMCDII +3403, MMMCDIII +3404, MMMCDIV +3405, MMMCDV +3406, MMMCDVI +3407, MMMCDVII +3408, MMMCDVIII +3409, MMMCDIX +3410, MMMCDX +3411, MMMCDXI +3412, MMMCDXII +3413, MMMCDXIII +3414, MMMCDXIV +3415, MMMCDXV +3416, MMMCDXVI +3417, MMMCDXVII +3418, MMMCDXVIII +3419, MMMCDXIX +3420, MMMCDXX +3421, MMMCDXXI +3422, MMMCDXXII +3423, MMMCDXXIII +3424, MMMCDXXIV +3425, MMMCDXXV +3426, MMMCDXXVI +3427, MMMCDXXVII +3428, MMMCDXXVIII +3429, MMMCDXXIX +3430, MMMCDXXX +3431, MMMCDXXXI +3432, MMMCDXXXII +3433, MMMCDXXXIII +3434, MMMCDXXXIV +3435, MMMCDXXXV +3436, MMMCDXXXVI +3437, MMMCDXXXVII +3438, MMMCDXXXVIII +3439, MMMCDXXXIX +3440, MMMCDXL +3441, MMMCDXLI +3442, MMMCDXLII +3443, MMMCDXLIII +3444, MMMCDXLIV +3445, MMMCDXLV +3446, MMMCDXLVI +3447, MMMCDXLVII +3448, MMMCDXLVIII +3449, MMMCDXLIX +3450, MMMCDL +3451, MMMCDLI +3452, MMMCDLII +3453, MMMCDLIII +3454, MMMCDLIV +3455, MMMCDLV +3456, MMMCDLVI +3457, MMMCDLVII +3458, MMMCDLVIII +3459, MMMCDLIX +3460, MMMCDLX +3461, MMMCDLXI +3462, MMMCDLXII +3463, MMMCDLXIII +3464, MMMCDLXIV +3465, MMMCDLXV +3466, MMMCDLXVI +3467, MMMCDLXVII +3468, MMMCDLXVIII +3469, MMMCDLXIX +3470, MMMCDLXX +3471, MMMCDLXXI +3472, MMMCDLXXII +3473, MMMCDLXXIII +3474, MMMCDLXXIV +3475, MMMCDLXXV +3476, MMMCDLXXVI +3477, MMMCDLXXVII +3478, MMMCDLXXVIII +3479, MMMCDLXXIX +3480, MMMCDLXXX +3481, MMMCDLXXXI +3482, MMMCDLXXXII +3483, MMMCDLXXXIII +3484, MMMCDLXXXIV +3485, MMMCDLXXXV +3486, MMMCDLXXXVI +3487, MMMCDLXXXVII +3488, MMMCDLXXXVIII +3489, MMMCDLXXXIX +3490, MMMCDXC +3491, MMMCDXCI +3492, MMMCDXCII +3493, MMMCDXCIII +3494, MMMCDXCIV +3495, MMMCDXCV +3496, MMMCDXCVI +3497, MMMCDXCVII +3498, MMMCDXCVIII +3499, MMMCDXCIX +3500, MMMD +3501, MMMDI +3502, MMMDII +3503, MMMDIII +3504, MMMDIV +3505, MMMDV +3506, MMMDVI +3507, MMMDVII +3508, MMMDVIII +3509, MMMDIX +3510, MMMDX +3511, MMMDXI +3512, MMMDXII +3513, MMMDXIII +3514, MMMDXIV +3515, MMMDXV +3516, MMMDXVI +3517, MMMDXVII +3518, MMMDXVIII +3519, MMMDXIX +3520, MMMDXX +3521, MMMDXXI +3522, MMMDXXII +3523, MMMDXXIII +3524, MMMDXXIV +3525, MMMDXXV +3526, MMMDXXVI +3527, MMMDXXVII +3528, MMMDXXVIII +3529, MMMDXXIX +3530, MMMDXXX +3531, MMMDXXXI +3532, MMMDXXXII +3533, MMMDXXXIII +3534, MMMDXXXIV +3535, MMMDXXXV +3536, MMMDXXXVI +3537, MMMDXXXVII +3538, MMMDXXXVIII +3539, MMMDXXXIX +3540, MMMDXL +3541, MMMDXLI +3542, MMMDXLII +3543, MMMDXLIII +3544, MMMDXLIV +3545, MMMDXLV +3546, MMMDXLVI +3547, MMMDXLVII +3548, MMMDXLVIII +3549, MMMDXLIX +3550, MMMDL +3551, MMMDLI +3552, MMMDLII +3553, MMMDLIII +3554, MMMDLIV +3555, MMMDLV +3556, MMMDLVI +3557, MMMDLVII +3558, MMMDLVIII +3559, MMMDLIX +3560, MMMDLX +3561, MMMDLXI +3562, MMMDLXII +3563, MMMDLXIII +3564, MMMDLXIV +3565, MMMDLXV +3566, MMMDLXVI +3567, MMMDLXVII +3568, MMMDLXVIII +3569, MMMDLXIX +3570, MMMDLXX +3571, MMMDLXXI +3572, MMMDLXXII +3573, MMMDLXXIII +3574, MMMDLXXIV +3575, MMMDLXXV +3576, MMMDLXXVI +3577, MMMDLXXVII +3578, MMMDLXXVIII +3579, MMMDLXXIX +3580, MMMDLXXX +3581, MMMDLXXXI +3582, MMMDLXXXII +3583, MMMDLXXXIII +3584, MMMDLXXXIV +3585, MMMDLXXXV +3586, MMMDLXXXVI +3587, MMMDLXXXVII +3588, MMMDLXXXVIII +3589, MMMDLXXXIX +3590, MMMDXC +3591, MMMDXCI +3592, MMMDXCII +3593, MMMDXCIII +3594, MMMDXCIV +3595, MMMDXCV +3596, MMMDXCVI +3597, MMMDXCVII +3598, MMMDXCVIII +3599, MMMDXCIX +3600, MMMDC +3601, MMMDCI +3602, MMMDCII +3603, MMMDCIII +3604, MMMDCIV +3605, MMMDCV +3606, MMMDCVI +3607, MMMDCVII +3608, MMMDCVIII +3609, MMMDCIX +3610, MMMDCX +3611, MMMDCXI +3612, MMMDCXII +3613, MMMDCXIII +3614, MMMDCXIV +3615, MMMDCXV +3616, MMMDCXVI +3617, MMMDCXVII +3618, MMMDCXVIII +3619, MMMDCXIX +3620, MMMDCXX +3621, MMMDCXXI +3622, MMMDCXXII +3623, MMMDCXXIII +3624, MMMDCXXIV +3625, MMMDCXXV +3626, MMMDCXXVI +3627, MMMDCXXVII +3628, MMMDCXXVIII +3629, MMMDCXXIX +3630, MMMDCXXX +3631, MMMDCXXXI +3632, MMMDCXXXII +3633, MMMDCXXXIII +3634, MMMDCXXXIV +3635, MMMDCXXXV +3636, MMMDCXXXVI +3637, MMMDCXXXVII +3638, MMMDCXXXVIII +3639, MMMDCXXXIX +3640, MMMDCXL +3641, MMMDCXLI +3642, MMMDCXLII +3643, MMMDCXLIII +3644, MMMDCXLIV +3645, MMMDCXLV +3646, MMMDCXLVI +3647, MMMDCXLVII +3648, MMMDCXLVIII +3649, MMMDCXLIX +3650, MMMDCL +3651, MMMDCLI +3652, MMMDCLII +3653, MMMDCLIII +3654, MMMDCLIV +3655, MMMDCLV +3656, MMMDCLVI +3657, MMMDCLVII +3658, MMMDCLVIII +3659, MMMDCLIX +3660, MMMDCLX +3661, MMMDCLXI +3662, MMMDCLXII +3663, MMMDCLXIII +3664, MMMDCLXIV +3665, MMMDCLXV +3666, MMMDCLXVI +3667, MMMDCLXVII +3668, MMMDCLXVIII +3669, MMMDCLXIX +3670, MMMDCLXX +3671, MMMDCLXXI +3672, MMMDCLXXII +3673, MMMDCLXXIII +3674, MMMDCLXXIV +3675, MMMDCLXXV +3676, MMMDCLXXVI +3677, MMMDCLXXVII +3678, MMMDCLXXVIII +3679, MMMDCLXXIX +3680, MMMDCLXXX +3681, MMMDCLXXXI +3682, MMMDCLXXXII +3683, MMMDCLXXXIII +3684, MMMDCLXXXIV +3685, MMMDCLXXXV +3686, MMMDCLXXXVI +3687, MMMDCLXXXVII +3688, MMMDCLXXXVIII +3689, MMMDCLXXXIX +3690, MMMDCXC +3691, MMMDCXCI +3692, MMMDCXCII +3693, MMMDCXCIII +3694, MMMDCXCIV +3695, MMMDCXCV +3696, MMMDCXCVI +3697, MMMDCXCVII +3698, MMMDCXCVIII +3699, MMMDCXCIX +3700, MMMDCC +3701, MMMDCCI +3702, MMMDCCII +3703, MMMDCCIII +3704, MMMDCCIV +3705, MMMDCCV +3706, MMMDCCVI +3707, MMMDCCVII +3708, MMMDCCVIII +3709, MMMDCCIX +3710, MMMDCCX +3711, MMMDCCXI +3712, MMMDCCXII +3713, MMMDCCXIII +3714, MMMDCCXIV +3715, MMMDCCXV +3716, MMMDCCXVI +3717, MMMDCCXVII +3718, MMMDCCXVIII +3719, MMMDCCXIX +3720, MMMDCCXX +3721, MMMDCCXXI +3722, MMMDCCXXII +3723, MMMDCCXXIII +3724, MMMDCCXXIV +3725, MMMDCCXXV +3726, MMMDCCXXVI +3727, MMMDCCXXVII +3728, MMMDCCXXVIII +3729, MMMDCCXXIX +3730, MMMDCCXXX +3731, MMMDCCXXXI +3732, MMMDCCXXXII +3733, MMMDCCXXXIII +3734, MMMDCCXXXIV +3735, MMMDCCXXXV +3736, MMMDCCXXXVI +3737, MMMDCCXXXVII +3738, MMMDCCXXXVIII +3739, MMMDCCXXXIX +3740, MMMDCCXL +3741, MMMDCCXLI +3742, MMMDCCXLII +3743, MMMDCCXLIII +3744, MMMDCCXLIV +3745, MMMDCCXLV +3746, MMMDCCXLVI +3747, MMMDCCXLVII +3748, MMMDCCXLVIII +3749, MMMDCCXLIX +3750, MMMDCCL +3751, MMMDCCLI +3752, MMMDCCLII +3753, MMMDCCLIII +3754, MMMDCCLIV +3755, MMMDCCLV +3756, MMMDCCLVI +3757, MMMDCCLVII +3758, MMMDCCLVIII +3759, MMMDCCLIX +3760, MMMDCCLX +3761, MMMDCCLXI +3762, MMMDCCLXII +3763, MMMDCCLXIII +3764, MMMDCCLXIV +3765, MMMDCCLXV +3766, MMMDCCLXVI +3767, MMMDCCLXVII +3768, MMMDCCLXVIII +3769, MMMDCCLXIX +3770, MMMDCCLXX +3771, MMMDCCLXXI +3772, MMMDCCLXXII +3773, MMMDCCLXXIII +3774, MMMDCCLXXIV +3775, MMMDCCLXXV +3776, MMMDCCLXXVI +3777, MMMDCCLXXVII +3778, MMMDCCLXXVIII +3779, MMMDCCLXXIX +3780, MMMDCCLXXX +3781, MMMDCCLXXXI +3782, MMMDCCLXXXII +3783, MMMDCCLXXXIII +3784, MMMDCCLXXXIV +3785, MMMDCCLXXXV +3786, MMMDCCLXXXVI +3787, MMMDCCLXXXVII +3788, MMMDCCLXXXVIII +3789, MMMDCCLXXXIX +3790, MMMDCCXC +3791, MMMDCCXCI +3792, MMMDCCXCII +3793, MMMDCCXCIII +3794, MMMDCCXCIV +3795, MMMDCCXCV +3796, MMMDCCXCVI +3797, MMMDCCXCVII +3798, MMMDCCXCVIII +3799, MMMDCCXCIX +3800, MMMDCCC +3801, MMMDCCCI +3802, MMMDCCCII +3803, MMMDCCCIII +3804, MMMDCCCIV +3805, MMMDCCCV +3806, MMMDCCCVI +3807, MMMDCCCVII +3808, MMMDCCCVIII +3809, MMMDCCCIX +3810, MMMDCCCX +3811, MMMDCCCXI +3812, MMMDCCCXII +3813, MMMDCCCXIII +3814, MMMDCCCXIV +3815, MMMDCCCXV +3816, MMMDCCCXVI +3817, MMMDCCCXVII +3818, MMMDCCCXVIII +3819, MMMDCCCXIX +3820, MMMDCCCXX +3821, MMMDCCCXXI +3822, MMMDCCCXXII +3823, MMMDCCCXXIII +3824, MMMDCCCXXIV +3825, MMMDCCCXXV +3826, MMMDCCCXXVI +3827, MMMDCCCXXVII +3828, MMMDCCCXXVIII +3829, MMMDCCCXXIX +3830, MMMDCCCXXX +3831, MMMDCCCXXXI +3832, MMMDCCCXXXII +3833, MMMDCCCXXXIII +3834, MMMDCCCXXXIV +3835, MMMDCCCXXXV +3836, MMMDCCCXXXVI +3837, MMMDCCCXXXVII +3838, MMMDCCCXXXVIII +3839, MMMDCCCXXXIX +3840, MMMDCCCXL +3841, MMMDCCCXLI +3842, MMMDCCCXLII +3843, MMMDCCCXLIII +3844, MMMDCCCXLIV +3845, MMMDCCCXLV +3846, MMMDCCCXLVI +3847, MMMDCCCXLVII +3848, MMMDCCCXLVIII +3849, MMMDCCCXLIX +3850, MMMDCCCL +3851, MMMDCCCLI +3852, MMMDCCCLII +3853, MMMDCCCLIII +3854, MMMDCCCLIV +3855, MMMDCCCLV +3856, MMMDCCCLVI +3857, MMMDCCCLVII +3858, MMMDCCCLVIII +3859, MMMDCCCLIX +3860, MMMDCCCLX +3861, MMMDCCCLXI +3862, MMMDCCCLXII +3863, MMMDCCCLXIII +3864, MMMDCCCLXIV +3865, MMMDCCCLXV +3866, MMMDCCCLXVI +3867, MMMDCCCLXVII +3868, MMMDCCCLXVIII +3869, MMMDCCCLXIX +3870, MMMDCCCLXX +3871, MMMDCCCLXXI +3872, MMMDCCCLXXII +3873, MMMDCCCLXXIII +3874, MMMDCCCLXXIV +3875, MMMDCCCLXXV +3876, MMMDCCCLXXVI +3877, MMMDCCCLXXVII +3878, MMMDCCCLXXVIII +3879, MMMDCCCLXXIX +3880, MMMDCCCLXXX +3881, MMMDCCCLXXXI +3882, MMMDCCCLXXXII +3883, MMMDCCCLXXXIII +3884, MMMDCCCLXXXIV +3885, MMMDCCCLXXXV +3886, MMMDCCCLXXXVI +3887, MMMDCCCLXXXVII +3888, MMMDCCCLXXXVIII +3889, MMMDCCCLXXXIX +3890, MMMDCCCXC +3891, MMMDCCCXCI +3892, MMMDCCCXCII +3893, MMMDCCCXCIII +3894, MMMDCCCXCIV +3895, MMMDCCCXCV +3896, MMMDCCCXCVI +3897, MMMDCCCXCVII +3898, MMMDCCCXCVIII +3899, MMMDCCCXCIX +3900, MMMCM +3901, MMMCMI +3902, MMMCMII +3903, MMMCMIII +3904, MMMCMIV +3905, MMMCMV +3906, MMMCMVI +3907, MMMCMVII +3908, MMMCMVIII +3909, MMMCMIX +3910, MMMCMX +3911, MMMCMXI +3912, MMMCMXII +3913, MMMCMXIII +3914, MMMCMXIV +3915, MMMCMXV +3916, MMMCMXVI +3917, MMMCMXVII +3918, MMMCMXVIII +3919, MMMCMXIX +3920, MMMCMXX +3921, MMMCMXXI +3922, MMMCMXXII +3923, MMMCMXXIII +3924, MMMCMXXIV +3925, MMMCMXXV +3926, MMMCMXXVI +3927, MMMCMXXVII +3928, MMMCMXXVIII +3929, MMMCMXXIX +3930, MMMCMXXX +3931, MMMCMXXXI +3932, MMMCMXXXII +3933, MMMCMXXXIII +3934, MMMCMXXXIV +3935, MMMCMXXXV +3936, MMMCMXXXVI +3937, MMMCMXXXVII +3938, MMMCMXXXVIII +3939, MMMCMXXXIX +3940, MMMCMXL +3941, MMMCMXLI +3942, MMMCMXLII +3943, MMMCMXLIII +3944, MMMCMXLIV +3945, MMMCMXLV +3946, MMMCMXLVI +3947, MMMCMXLVII +3948, MMMCMXLVIII +3949, MMMCMXLIX +3950, MMMCML +3951, MMMCMLI +3952, MMMCMLII +3953, MMMCMLIII +3954, MMMCMLIV +3955, MMMCMLV +3956, MMMCMLVI +3957, MMMCMLVII +3958, MMMCMLVIII +3959, MMMCMLIX +3960, MMMCMLX +3961, MMMCMLXI +3962, MMMCMLXII +3963, MMMCMLXIII +3964, MMMCMLXIV +3965, MMMCMLXV +3966, MMMCMLXVI +3967, MMMCMLXVII +3968, MMMCMLXVIII +3969, MMMCMLXIX +3970, MMMCMLXX +3971, MMMCMLXXI +3972, MMMCMLXXII +3973, MMMCMLXXIII +3974, MMMCMLXXIV +3975, MMMCMLXXV +3976, MMMCMLXXVI +3977, MMMCMLXXVII +3978, MMMCMLXXVIII +3979, MMMCMLXXIX +3980, MMMCMLXXX +3981, MMMCMLXXXI +3982, MMMCMLXXXII +3983, MMMCMLXXXIII +3984, MMMCMLXXXIV +3985, MMMCMLXXXV +3986, MMMCMLXXXVI +3987, MMMCMLXXXVII +3988, MMMCMLXXXVIII +3989, MMMCMLXXXIX +3990, MMMCMXC +3991, MMMCMXCI +3992, MMMCMXCII +3993, MMMCMXCIII +3994, MMMCMXCIV +3995, MMMCMXCV +3996, MMMCMXCVI +3997, MMMCMXCVII +3998, MMMCMXCVIII +3999, MMMCMXCIX diff --git a/src/test/resources/cases/roman/numerals.txt b/src/test/resources/cases/roman/numerals.txt deleted file mode 100644 index 54d4d3f5..00000000 --- a/src/test/resources/cases/roman/numerals.txt +++ /dev/null @@ -1,3999 +0,0 @@ -1 I -2 II -3 III -4 IV -5 V -6 VI -7 VII -8 VIII -9 IX -10 X -11 XI -12 XII -13 XIII -14 XIV -15 XV -16 XVI -17 XVII -18 XVIII -19 XIX -20 XX -21 XXI -22 XXII -23 XXIII -24 XXIV -25 XXV -26 XXVI -27 XXVII -28 XXVIII -29 XXIX -30 XXX -31 XXXI -32 XXXII -33 XXXIII -34 XXXIV -35 XXXV -36 XXXVI -37 XXXVII -38 XXXVIII -39 XXXIX -40 XL -41 XLI -42 XLII -43 XLIII -44 XLIV -45 XLV -46 XLVI -47 XLVII -48 XLVIII -49 XLIX -50 L -51 LI -52 LII -53 LIII -54 LIV -55 LV -56 LVI -57 LVII -58 LVIII -59 LIX -60 LX -61 LXI -62 LXII -63 LXIII -64 LXIV -65 LXV -66 LXVI -67 LXVII -68 LXVIII -69 LXIX -70 LXX -71 LXXI -72 LXXII -73 LXXIII -74 LXXIV -75 LXXV -76 LXXVI -77 LXXVII -78 LXXVIII -79 LXXIX -80 LXXX -81 LXXXI -82 LXXXII -83 LXXXIII -84 LXXXIV -85 LXXXV -86 LXXXVI -87 LXXXVII -88 LXXXVIII -89 LXXXIX -90 XC -91 XCI -92 XCII -93 XCIII -94 XCIV -95 XCV -96 XCVI -97 XCVII -98 XCVIII -99 XCIX -100 C -101 CI -102 CII -103 CIII -104 CIV -105 CV -106 CVI -107 CVII -108 CVIII -109 CIX -110 CX -111 CXI -112 CXII -113 CXIII -114 CXIV -115 CXV -116 CXVI -117 CXVII -118 CXVIII -119 CXIX -120 CXX -121 CXXI -122 CXXII -123 CXXIII -124 CXXIV -125 CXXV -126 CXXVI -127 CXXVII -128 CXXVIII -129 CXXIX -130 CXXX -131 CXXXI -132 CXXXII -133 CXXXIII -134 CXXXIV -135 CXXXV -136 CXXXVI -137 CXXXVII -138 CXXXVIII -139 CXXXIX -140 CXL -141 CXLI -142 CXLII -143 CXLIII -144 CXLIV -145 CXLV -146 CXLVI -147 CXLVII -148 CXLVIII -149 CXLIX -150 CL -151 CLI -152 CLII -153 CLIII -154 CLIV -155 CLV -156 CLVI -157 CLVII -158 CLVIII -159 CLIX -160 CLX -161 CLXI -162 CLXII -163 CLXIII -164 CLXIV -165 CLXV -166 CLXVI -167 CLXVII -168 CLXVIII -169 CLXIX -170 CLXX -171 CLXXI -172 CLXXII -173 CLXXIII -174 CLXXIV -175 CLXXV -176 CLXXVI -177 CLXXVII -178 CLXXVIII -179 CLXXIX -180 CLXXX -181 CLXXXI -182 CLXXXII -183 CLXXXIII -184 CLXXXIV -185 CLXXXV -186 CLXXXVI -187 CLXXXVII -188 CLXXXVIII -189 CLXXXIX -190 CXC -191 CXCI -192 CXCII -193 CXCIII -194 CXCIV -195 CXCV -196 CXCVI -197 CXCVII -198 CXCVIII -199 CXCIX -200 CC -201 CCI -202 CCII -203 CCIII -204 CCIV -205 CCV -206 CCVI -207 CCVII -208 CCVIII -209 CCIX -210 CCX -211 CCXI -212 CCXII -213 CCXIII -214 CCXIV -215 CCXV -216 CCXVI -217 CCXVII -218 CCXVIII -219 CCXIX -220 CCXX -221 CCXXI -222 CCXXII -223 CCXXIII -224 CCXXIV -225 CCXXV -226 CCXXVI -227 CCXXVII -228 CCXXVIII -229 CCXXIX -230 CCXXX -231 CCXXXI -232 CCXXXII -233 CCXXXIII -234 CCXXXIV -235 CCXXXV -236 CCXXXVI -237 CCXXXVII -238 CCXXXVIII -239 CCXXXIX -240 CCXL -241 CCXLI -242 CCXLII -243 CCXLIII -244 CCXLIV -245 CCXLV -246 CCXLVI -247 CCXLVII -248 CCXLVIII -249 CCXLIX -250 CCL -251 CCLI -252 CCLII -253 CCLIII -254 CCLIV -255 CCLV -256 CCLVI -257 CCLVII -258 CCLVIII -259 CCLIX -260 CCLX -261 CCLXI -262 CCLXII -263 CCLXIII -264 CCLXIV -265 CCLXV -266 CCLXVI -267 CCLXVII -268 CCLXVIII -269 CCLXIX -270 CCLXX -271 CCLXXI -272 CCLXXII -273 CCLXXIII -274 CCLXXIV -275 CCLXXV -276 CCLXXVI -277 CCLXXVII -278 CCLXXVIII -279 CCLXXIX -280 CCLXXX -281 CCLXXXI -282 CCLXXXII -283 CCLXXXIII -284 CCLXXXIV -285 CCLXXXV -286 CCLXXXVI -287 CCLXXXVII -288 CCLXXXVIII -289 CCLXXXIX -290 CCXC -291 CCXCI -292 CCXCII -293 CCXCIII -294 CCXCIV -295 CCXCV -296 CCXCVI -297 CCXCVII -298 CCXCVIII -299 CCXCIX -300 CCC -301 CCCI -302 CCCII -303 CCCIII -304 CCCIV -305 CCCV -306 CCCVI -307 CCCVII -308 CCCVIII -309 CCCIX -310 CCCX -311 CCCXI -312 CCCXII -313 CCCXIII -314 CCCXIV -315 CCCXV -316 CCCXVI -317 CCCXVII -318 CCCXVIII -319 CCCXIX -320 CCCXX -321 CCCXXI -322 CCCXXII -323 CCCXXIII -324 CCCXXIV -325 CCCXXV -326 CCCXXVI -327 CCCXXVII -328 CCCXXVIII -329 CCCXXIX -330 CCCXXX -331 CCCXXXI -332 CCCXXXII -333 CCCXXXIII -334 CCCXXXIV -335 CCCXXXV -336 CCCXXXVI -337 CCCXXXVII -338 CCCXXXVIII -339 CCCXXXIX -340 CCCXL -341 CCCXLI -342 CCCXLII -343 CCCXLIII -344 CCCXLIV -345 CCCXLV -346 CCCXLVI -347 CCCXLVII -348 CCCXLVIII -349 CCCXLIX -350 CCCL -351 CCCLI -352 CCCLII -353 CCCLIII -354 CCCLIV -355 CCCLV -356 CCCLVI -357 CCCLVII -358 CCCLVIII -359 CCCLIX -360 CCCLX -361 CCCLXI -362 CCCLXII -363 CCCLXIII -364 CCCLXIV -365 CCCLXV -366 CCCLXVI -367 CCCLXVII -368 CCCLXVIII -369 CCCLXIX -370 CCCLXX -371 CCCLXXI -372 CCCLXXII -373 CCCLXXIII -374 CCCLXXIV -375 CCCLXXV -376 CCCLXXVI -377 CCCLXXVII -378 CCCLXXVIII -379 CCCLXXIX -380 CCCLXXX -381 CCCLXXXI -382 CCCLXXXII -383 CCCLXXXIII -384 CCCLXXXIV -385 CCCLXXXV -386 CCCLXXXVI -387 CCCLXXXVII -388 CCCLXXXVIII -389 CCCLXXXIX -390 CCCXC -391 CCCXCI -392 CCCXCII -393 CCCXCIII -394 CCCXCIV -395 CCCXCV -396 CCCXCVI -397 CCCXCVII -398 CCCXCVIII -399 CCCXCIX -400 CD -401 CDI -402 CDII -403 CDIII -404 CDIV -405 CDV -406 CDVI -407 CDVII -408 CDVIII -409 CDIX -410 CDX -411 CDXI -412 CDXII -413 CDXIII -414 CDXIV -415 CDXV -416 CDXVI -417 CDXVII -418 CDXVIII -419 CDXIX -420 CDXX -421 CDXXI -422 CDXXII -423 CDXXIII -424 CDXXIV -425 CDXXV -426 CDXXVI -427 CDXXVII -428 CDXXVIII -429 CDXXIX -430 CDXXX -431 CDXXXI -432 CDXXXII -433 CDXXXIII -434 CDXXXIV -435 CDXXXV -436 CDXXXVI -437 CDXXXVII -438 CDXXXVIII -439 CDXXXIX -440 CDXL -441 CDXLI -442 CDXLII -443 CDXLIII -444 CDXLIV -445 CDXLV -446 CDXLVI -447 CDXLVII -448 CDXLVIII -449 CDXLIX -450 CDL -451 CDLI -452 CDLII -453 CDLIII -454 CDLIV -455 CDLV -456 CDLVI -457 CDLVII -458 CDLVIII -459 CDLIX -460 CDLX -461 CDLXI -462 CDLXII -463 CDLXIII -464 CDLXIV -465 CDLXV -466 CDLXVI -467 CDLXVII -468 CDLXVIII -469 CDLXIX -470 CDLXX -471 CDLXXI -472 CDLXXII -473 CDLXXIII -474 CDLXXIV -475 CDLXXV -476 CDLXXVI -477 CDLXXVII -478 CDLXXVIII -479 CDLXXIX -480 CDLXXX -481 CDLXXXI -482 CDLXXXII -483 CDLXXXIII -484 CDLXXXIV -485 CDLXXXV -486 CDLXXXVI -487 CDLXXXVII -488 CDLXXXVIII -489 CDLXXXIX -490 CDXC -491 CDXCI -492 CDXCII -493 CDXCIII -494 CDXCIV -495 CDXCV -496 CDXCVI -497 CDXCVII -498 CDXCVIII -499 CDXCIX -500 D -501 DI -502 DII -503 DIII -504 DIV -505 DV -506 DVI -507 DVII -508 DVIII -509 DIX -510 DX -511 DXI -512 DXII -513 DXIII -514 DXIV -515 DXV -516 DXVI -517 DXVII -518 DXVIII -519 DXIX -520 DXX -521 DXXI -522 DXXII -523 DXXIII -524 DXXIV -525 DXXV -526 DXXVI -527 DXXVII -528 DXXVIII -529 DXXIX -530 DXXX -531 DXXXI -532 DXXXII -533 DXXXIII -534 DXXXIV -535 DXXXV -536 DXXXVI -537 DXXXVII -538 DXXXVIII -539 DXXXIX -540 DXL -541 DXLI -542 DXLII -543 DXLIII -544 DXLIV -545 DXLV -546 DXLVI -547 DXLVII -548 DXLVIII -549 DXLIX -550 DL -551 DLI -552 DLII -553 DLIII -554 DLIV -555 DLV -556 DLVI -557 DLVII -558 DLVIII -559 DLIX -560 DLX -561 DLXI -562 DLXII -563 DLXIII -564 DLXIV -565 DLXV -566 DLXVI -567 DLXVII -568 DLXVIII -569 DLXIX -570 DLXX -571 DLXXI -572 DLXXII -573 DLXXIII -574 DLXXIV -575 DLXXV -576 DLXXVI -577 DLXXVII -578 DLXXVIII -579 DLXXIX -580 DLXXX -581 DLXXXI -582 DLXXXII -583 DLXXXIII -584 DLXXXIV -585 DLXXXV -586 DLXXXVI -587 DLXXXVII -588 DLXXXVIII -589 DLXXXIX -590 DXC -591 DXCI -592 DXCII -593 DXCIII -594 DXCIV -595 DXCV -596 DXCVI -597 DXCVII -598 DXCVIII -599 DXCIX -600 DC -601 DCI -602 DCII -603 DCIII -604 DCIV -605 DCV -606 DCVI -607 DCVII -608 DCVIII -609 DCIX -610 DCX -611 DCXI -612 DCXII -613 DCXIII -614 DCXIV -615 DCXV -616 DCXVI -617 DCXVII -618 DCXVIII -619 DCXIX -620 DCXX -621 DCXXI -622 DCXXII -623 DCXXIII -624 DCXXIV -625 DCXXV -626 DCXXVI -627 DCXXVII -628 DCXXVIII -629 DCXXIX -630 DCXXX -631 DCXXXI -632 DCXXXII -633 DCXXXIII -634 DCXXXIV -635 DCXXXV -636 DCXXXVI -637 DCXXXVII -638 DCXXXVIII -639 DCXXXIX -640 DCXL -641 DCXLI -642 DCXLII -643 DCXLIII -644 DCXLIV -645 DCXLV -646 DCXLVI -647 DCXLVII -648 DCXLVIII -649 DCXLIX -650 DCL -651 DCLI -652 DCLII -653 DCLIII -654 DCLIV -655 DCLV -656 DCLVI -657 DCLVII -658 DCLVIII -659 DCLIX -660 DCLX -661 DCLXI -662 DCLXII -663 DCLXIII -664 DCLXIV -665 DCLXV -666 DCLXVI -667 DCLXVII -668 DCLXVIII -669 DCLXIX -670 DCLXX -671 DCLXXI -672 DCLXXII -673 DCLXXIII -674 DCLXXIV -675 DCLXXV -676 DCLXXVI -677 DCLXXVII -678 DCLXXVIII -679 DCLXXIX -680 DCLXXX -681 DCLXXXI -682 DCLXXXII -683 DCLXXXIII -684 DCLXXXIV -685 DCLXXXV -686 DCLXXXVI -687 DCLXXXVII -688 DCLXXXVIII -689 DCLXXXIX -690 DCXC -691 DCXCI -692 DCXCII -693 DCXCIII -694 DCXCIV -695 DCXCV -696 DCXCVI -697 DCXCVII -698 DCXCVIII -699 DCXCIX -700 DCC -701 DCCI -702 DCCII -703 DCCIII -704 DCCIV -705 DCCV -706 DCCVI -707 DCCVII -708 DCCVIII -709 DCCIX -710 DCCX -711 DCCXI -712 DCCXII -713 DCCXIII -714 DCCXIV -715 DCCXV -716 DCCXVI -717 DCCXVII -718 DCCXVIII -719 DCCXIX -720 DCCXX -721 DCCXXI -722 DCCXXII -723 DCCXXIII -724 DCCXXIV -725 DCCXXV -726 DCCXXVI -727 DCCXXVII -728 DCCXXVIII -729 DCCXXIX -730 DCCXXX -731 DCCXXXI -732 DCCXXXII -733 DCCXXXIII -734 DCCXXXIV -735 DCCXXXV -736 DCCXXXVI -737 DCCXXXVII -738 DCCXXXVIII -739 DCCXXXIX -740 DCCXL -741 DCCXLI -742 DCCXLII -743 DCCXLIII -744 DCCXLIV -745 DCCXLV -746 DCCXLVI -747 DCCXLVII -748 DCCXLVIII -749 DCCXLIX -750 DCCL -751 DCCLI -752 DCCLII -753 DCCLIII -754 DCCLIV -755 DCCLV -756 DCCLVI -757 DCCLVII -758 DCCLVIII -759 DCCLIX -760 DCCLX -761 DCCLXI -762 DCCLXII -763 DCCLXIII -764 DCCLXIV -765 DCCLXV -766 DCCLXVI -767 DCCLXVII -768 DCCLXVIII -769 DCCLXIX -770 DCCLXX -771 DCCLXXI -772 DCCLXXII -773 DCCLXXIII -774 DCCLXXIV -775 DCCLXXV -776 DCCLXXVI -777 DCCLXXVII -778 DCCLXXVIII -779 DCCLXXIX -780 DCCLXXX -781 DCCLXXXI -782 DCCLXXXII -783 DCCLXXXIII -784 DCCLXXXIV -785 DCCLXXXV -786 DCCLXXXVI -787 DCCLXXXVII -788 DCCLXXXVIII -789 DCCLXXXIX -790 DCCXC -791 DCCXCI -792 DCCXCII -793 DCCXCIII -794 DCCXCIV -795 DCCXCV -796 DCCXCVI -797 DCCXCVII -798 DCCXCVIII -799 DCCXCIX -800 DCCC -801 DCCCI -802 DCCCII -803 DCCCIII -804 DCCCIV -805 DCCCV -806 DCCCVI -807 DCCCVII -808 DCCCVIII -809 DCCCIX -810 DCCCX -811 DCCCXI -812 DCCCXII -813 DCCCXIII -814 DCCCXIV -815 DCCCXV -816 DCCCXVI -817 DCCCXVII -818 DCCCXVIII -819 DCCCXIX -820 DCCCXX -821 DCCCXXI -822 DCCCXXII -823 DCCCXXIII -824 DCCCXXIV -825 DCCCXXV -826 DCCCXXVI -827 DCCCXXVII -828 DCCCXXVIII -829 DCCCXXIX -830 DCCCXXX -831 DCCCXXXI -832 DCCCXXXII -833 DCCCXXXIII -834 DCCCXXXIV -835 DCCCXXXV -836 DCCCXXXVI -837 DCCCXXXVII -838 DCCCXXXVIII -839 DCCCXXXIX -840 DCCCXL -841 DCCCXLI -842 DCCCXLII -843 DCCCXLIII -844 DCCCXLIV -845 DCCCXLV -846 DCCCXLVI -847 DCCCXLVII -848 DCCCXLVIII -849 DCCCXLIX -850 DCCCL -851 DCCCLI -852 DCCCLII -853 DCCCLIII -854 DCCCLIV -855 DCCCLV -856 DCCCLVI -857 DCCCLVII -858 DCCCLVIII -859 DCCCLIX -860 DCCCLX -861 DCCCLXI -862 DCCCLXII -863 DCCCLXIII -864 DCCCLXIV -865 DCCCLXV -866 DCCCLXVI -867 DCCCLXVII -868 DCCCLXVIII -869 DCCCLXIX -870 DCCCLXX -871 DCCCLXXI -872 DCCCLXXII -873 DCCCLXXIII -874 DCCCLXXIV -875 DCCCLXXV -876 DCCCLXXVI -877 DCCCLXXVII -878 DCCCLXXVIII -879 DCCCLXXIX -880 DCCCLXXX -881 DCCCLXXXI -882 DCCCLXXXII -883 DCCCLXXXIII -884 DCCCLXXXIV -885 DCCCLXXXV -886 DCCCLXXXVI -887 DCCCLXXXVII -888 DCCCLXXXVIII -889 DCCCLXXXIX -890 DCCCXC -891 DCCCXCI -892 DCCCXCII -893 DCCCXCIII -894 DCCCXCIV -895 DCCCXCV -896 DCCCXCVI -897 DCCCXCVII -898 DCCCXCVIII -899 DCCCXCIX -900 CM -901 CMI -902 CMII -903 CMIII -904 CMIV -905 CMV -906 CMVI -907 CMVII -908 CMVIII -909 CMIX -910 CMX -911 CMXI -912 CMXII -913 CMXIII -914 CMXIV -915 CMXV -916 CMXVI -917 CMXVII -918 CMXVIII -919 CMXIX -920 CMXX -921 CMXXI -922 CMXXII -923 CMXXIII -924 CMXXIV -925 CMXXV -926 CMXXVI -927 CMXXVII -928 CMXXVIII -929 CMXXIX -930 CMXXX -931 CMXXXI -932 CMXXXII -933 CMXXXIII -934 CMXXXIV -935 CMXXXV -936 CMXXXVI -937 CMXXXVII -938 CMXXXVIII -939 CMXXXIX -940 CMXL -941 CMXLI -942 CMXLII -943 CMXLIII -944 CMXLIV -945 CMXLV -946 CMXLVI -947 CMXLVII -948 CMXLVIII -949 CMXLIX -950 CML -951 CMLI -952 CMLII -953 CMLIII -954 CMLIV -955 CMLV -956 CMLVI -957 CMLVII -958 CMLVIII -959 CMLIX -960 CMLX -961 CMLXI -962 CMLXII -963 CMLXIII -964 CMLXIV -965 CMLXV -966 CMLXVI -967 CMLXVII -968 CMLXVIII -969 CMLXIX -970 CMLXX -971 CMLXXI -972 CMLXXII -973 CMLXXIII -974 CMLXXIV -975 CMLXXV -976 CMLXXVI -977 CMLXXVII -978 CMLXXVIII -979 CMLXXIX -980 CMLXXX -981 CMLXXXI -982 CMLXXXII -983 CMLXXXIII -984 CMLXXXIV -985 CMLXXXV -986 CMLXXXVI -987 CMLXXXVII -988 CMLXXXVIII -989 CMLXXXIX -990 CMXC -991 CMXCI -992 CMXCII -993 CMXCIII -994 CMXCIV -995 CMXCV -996 CMXCVI -997 CMXCVII -998 CMXCVIII -999 CMXCIX -1000 M -1001 MI -1002 MII -1003 MIII -1004 MIV -1005 MV -1006 MVI -1007 MVII -1008 MVIII -1009 MIX -1010 MX -1011 MXI -1012 MXII -1013 MXIII -1014 MXIV -1015 MXV -1016 MXVI -1017 MXVII -1018 MXVIII -1019 MXIX -1020 MXX -1021 MXXI -1022 MXXII -1023 MXXIII -1024 MXXIV -1025 MXXV -1026 MXXVI -1027 MXXVII -1028 MXXVIII -1029 MXXIX -1030 MXXX -1031 MXXXI -1032 MXXXII -1033 MXXXIII -1034 MXXXIV -1035 MXXXV -1036 MXXXVI -1037 MXXXVII -1038 MXXXVIII -1039 MXXXIX -1040 MXL -1041 MXLI -1042 MXLII -1043 MXLIII -1044 MXLIV -1045 MXLV -1046 MXLVI -1047 MXLVII -1048 MXLVIII -1049 MXLIX -1050 ML -1051 MLI -1052 MLII -1053 MLIII -1054 MLIV -1055 MLV -1056 MLVI -1057 MLVII -1058 MLVIII -1059 MLIX -1060 MLX -1061 MLXI -1062 MLXII -1063 MLXIII -1064 MLXIV -1065 MLXV -1066 MLXVI -1067 MLXVII -1068 MLXVIII -1069 MLXIX -1070 MLXX -1071 MLXXI -1072 MLXXII -1073 MLXXIII -1074 MLXXIV -1075 MLXXV -1076 MLXXVI -1077 MLXXVII -1078 MLXXVIII -1079 MLXXIX -1080 MLXXX -1081 MLXXXI -1082 MLXXXII -1083 MLXXXIII -1084 MLXXXIV -1085 MLXXXV -1086 MLXXXVI -1087 MLXXXVII -1088 MLXXXVIII -1089 MLXXXIX -1090 MXC -1091 MXCI -1092 MXCII -1093 MXCIII -1094 MXCIV -1095 MXCV -1096 MXCVI -1097 MXCVII -1098 MXCVIII -1099 MXCIX -1100 MC -1101 MCI -1102 MCII -1103 MCIII -1104 MCIV -1105 MCV -1106 MCVI -1107 MCVII -1108 MCVIII -1109 MCIX -1110 MCX -1111 MCXI -1112 MCXII -1113 MCXIII -1114 MCXIV -1115 MCXV -1116 MCXVI -1117 MCXVII -1118 MCXVIII -1119 MCXIX -1120 MCXX -1121 MCXXI -1122 MCXXII -1123 MCXXIII -1124 MCXXIV -1125 MCXXV -1126 MCXXVI -1127 MCXXVII -1128 MCXXVIII -1129 MCXXIX -1130 MCXXX -1131 MCXXXI -1132 MCXXXII -1133 MCXXXIII -1134 MCXXXIV -1135 MCXXXV -1136 MCXXXVI -1137 MCXXXVII -1138 MCXXXVIII -1139 MCXXXIX -1140 MCXL -1141 MCXLI -1142 MCXLII -1143 MCXLIII -1144 MCXLIV -1145 MCXLV -1146 MCXLVI -1147 MCXLVII -1148 MCXLVIII -1149 MCXLIX -1150 MCL -1151 MCLI -1152 MCLII -1153 MCLIII -1154 MCLIV -1155 MCLV -1156 MCLVI -1157 MCLVII -1158 MCLVIII -1159 MCLIX -1160 MCLX -1161 MCLXI -1162 MCLXII -1163 MCLXIII -1164 MCLXIV -1165 MCLXV -1166 MCLXVI -1167 MCLXVII -1168 MCLXVIII -1169 MCLXIX -1170 MCLXX -1171 MCLXXI -1172 MCLXXII -1173 MCLXXIII -1174 MCLXXIV -1175 MCLXXV -1176 MCLXXVI -1177 MCLXXVII -1178 MCLXXVIII -1179 MCLXXIX -1180 MCLXXX -1181 MCLXXXI -1182 MCLXXXII -1183 MCLXXXIII -1184 MCLXXXIV -1185 MCLXXXV -1186 MCLXXXVI -1187 MCLXXXVII -1188 MCLXXXVIII -1189 MCLXXXIX -1190 MCXC -1191 MCXCI -1192 MCXCII -1193 MCXCIII -1194 MCXCIV -1195 MCXCV -1196 MCXCVI -1197 MCXCVII -1198 MCXCVIII -1199 MCXCIX -1200 MCC -1201 MCCI -1202 MCCII -1203 MCCIII -1204 MCCIV -1205 MCCV -1206 MCCVI -1207 MCCVII -1208 MCCVIII -1209 MCCIX -1210 MCCX -1211 MCCXI -1212 MCCXII -1213 MCCXIII -1214 MCCXIV -1215 MCCXV -1216 MCCXVI -1217 MCCXVII -1218 MCCXVIII -1219 MCCXIX -1220 MCCXX -1221 MCCXXI -1222 MCCXXII -1223 MCCXXIII -1224 MCCXXIV -1225 MCCXXV -1226 MCCXXVI -1227 MCCXXVII -1228 MCCXXVIII -1229 MCCXXIX -1230 MCCXXX -1231 MCCXXXI -1232 MCCXXXII -1233 MCCXXXIII -1234 MCCXXXIV -1235 MCCXXXV -1236 MCCXXXVI -1237 MCCXXXVII -1238 MCCXXXVIII -1239 MCCXXXIX -1240 MCCXL -1241 MCCXLI -1242 MCCXLII -1243 MCCXLIII -1244 MCCXLIV -1245 MCCXLV -1246 MCCXLVI -1247 MCCXLVII -1248 MCCXLVIII -1249 MCCXLIX -1250 MCCL -1251 MCCLI -1252 MCCLII -1253 MCCLIII -1254 MCCLIV -1255 MCCLV -1256 MCCLVI -1257 MCCLVII -1258 MCCLVIII -1259 MCCLIX -1260 MCCLX -1261 MCCLXI -1262 MCCLXII -1263 MCCLXIII -1264 MCCLXIV -1265 MCCLXV -1266 MCCLXVI -1267 MCCLXVII -1268 MCCLXVIII -1269 MCCLXIX -1270 MCCLXX -1271 MCCLXXI -1272 MCCLXXII -1273 MCCLXXIII -1274 MCCLXXIV -1275 MCCLXXV -1276 MCCLXXVI -1277 MCCLXXVII -1278 MCCLXXVIII -1279 MCCLXXIX -1280 MCCLXXX -1281 MCCLXXXI -1282 MCCLXXXII -1283 MCCLXXXIII -1284 MCCLXXXIV -1285 MCCLXXXV -1286 MCCLXXXVI -1287 MCCLXXXVII -1288 MCCLXXXVIII -1289 MCCLXXXIX -1290 MCCXC -1291 MCCXCI -1292 MCCXCII -1293 MCCXCIII -1294 MCCXCIV -1295 MCCXCV -1296 MCCXCVI -1297 MCCXCVII -1298 MCCXCVIII -1299 MCCXCIX -1300 MCCC -1301 MCCCI -1302 MCCCII -1303 MCCCIII -1304 MCCCIV -1305 MCCCV -1306 MCCCVI -1307 MCCCVII -1308 MCCCVIII -1309 MCCCIX -1310 MCCCX -1311 MCCCXI -1312 MCCCXII -1313 MCCCXIII -1314 MCCCXIV -1315 MCCCXV -1316 MCCCXVI -1317 MCCCXVII -1318 MCCCXVIII -1319 MCCCXIX -1320 MCCCXX -1321 MCCCXXI -1322 MCCCXXII -1323 MCCCXXIII -1324 MCCCXXIV -1325 MCCCXXV -1326 MCCCXXVI -1327 MCCCXXVII -1328 MCCCXXVIII -1329 MCCCXXIX -1330 MCCCXXX -1331 MCCCXXXI -1332 MCCCXXXII -1333 MCCCXXXIII -1334 MCCCXXXIV -1335 MCCCXXXV -1336 MCCCXXXVI -1337 MCCCXXXVII -1338 MCCCXXXVIII -1339 MCCCXXXIX -1340 MCCCXL -1341 MCCCXLI -1342 MCCCXLII -1343 MCCCXLIII -1344 MCCCXLIV -1345 MCCCXLV -1346 MCCCXLVI -1347 MCCCXLVII -1348 MCCCXLVIII -1349 MCCCXLIX -1350 MCCCL -1351 MCCCLI -1352 MCCCLII -1353 MCCCLIII -1354 MCCCLIV -1355 MCCCLV -1356 MCCCLVI -1357 MCCCLVII -1358 MCCCLVIII -1359 MCCCLIX -1360 MCCCLX -1361 MCCCLXI -1362 MCCCLXII -1363 MCCCLXIII -1364 MCCCLXIV -1365 MCCCLXV -1366 MCCCLXVI -1367 MCCCLXVII -1368 MCCCLXVIII -1369 MCCCLXIX -1370 MCCCLXX -1371 MCCCLXXI -1372 MCCCLXXII -1373 MCCCLXXIII -1374 MCCCLXXIV -1375 MCCCLXXV -1376 MCCCLXXVI -1377 MCCCLXXVII -1378 MCCCLXXVIII -1379 MCCCLXXIX -1380 MCCCLXXX -1381 MCCCLXXXI -1382 MCCCLXXXII -1383 MCCCLXXXIII -1384 MCCCLXXXIV -1385 MCCCLXXXV -1386 MCCCLXXXVI -1387 MCCCLXXXVII -1388 MCCCLXXXVIII -1389 MCCCLXXXIX -1390 MCCCXC -1391 MCCCXCI -1392 MCCCXCII -1393 MCCCXCIII -1394 MCCCXCIV -1395 MCCCXCV -1396 MCCCXCVI -1397 MCCCXCVII -1398 MCCCXCVIII -1399 MCCCXCIX -1400 MCD -1401 MCDI -1402 MCDII -1403 MCDIII -1404 MCDIV -1405 MCDV -1406 MCDVI -1407 MCDVII -1408 MCDVIII -1409 MCDIX -1410 MCDX -1411 MCDXI -1412 MCDXII -1413 MCDXIII -1414 MCDXIV -1415 MCDXV -1416 MCDXVI -1417 MCDXVII -1418 MCDXVIII -1419 MCDXIX -1420 MCDXX -1421 MCDXXI -1422 MCDXXII -1423 MCDXXIII -1424 MCDXXIV -1425 MCDXXV -1426 MCDXXVI -1427 MCDXXVII -1428 MCDXXVIII -1429 MCDXXIX -1430 MCDXXX -1431 MCDXXXI -1432 MCDXXXII -1433 MCDXXXIII -1434 MCDXXXIV -1435 MCDXXXV -1436 MCDXXXVI -1437 MCDXXXVII -1438 MCDXXXVIII -1439 MCDXXXIX -1440 MCDXL -1441 MCDXLI -1442 MCDXLII -1443 MCDXLIII -1444 MCDXLIV -1445 MCDXLV -1446 MCDXLVI -1447 MCDXLVII -1448 MCDXLVIII -1449 MCDXLIX -1450 MCDL -1451 MCDLI -1452 MCDLII -1453 MCDLIII -1454 MCDLIV -1455 MCDLV -1456 MCDLVI -1457 MCDLVII -1458 MCDLVIII -1459 MCDLIX -1460 MCDLX -1461 MCDLXI -1462 MCDLXII -1463 MCDLXIII -1464 MCDLXIV -1465 MCDLXV -1466 MCDLXVI -1467 MCDLXVII -1468 MCDLXVIII -1469 MCDLXIX -1470 MCDLXX -1471 MCDLXXI -1472 MCDLXXII -1473 MCDLXXIII -1474 MCDLXXIV -1475 MCDLXXV -1476 MCDLXXVI -1477 MCDLXXVII -1478 MCDLXXVIII -1479 MCDLXXIX -1480 MCDLXXX -1481 MCDLXXXI -1482 MCDLXXXII -1483 MCDLXXXIII -1484 MCDLXXXIV -1485 MCDLXXXV -1486 MCDLXXXVI -1487 MCDLXXXVII -1488 MCDLXXXVIII -1489 MCDLXXXIX -1490 MCDXC -1491 MCDXCI -1492 MCDXCII -1493 MCDXCIII -1494 MCDXCIV -1495 MCDXCV -1496 MCDXCVI -1497 MCDXCVII -1498 MCDXCVIII -1499 MCDXCIX -1500 MD -1501 MDI -1502 MDII -1503 MDIII -1504 MDIV -1505 MDV -1506 MDVI -1507 MDVII -1508 MDVIII -1509 MDIX -1510 MDX -1511 MDXI -1512 MDXII -1513 MDXIII -1514 MDXIV -1515 MDXV -1516 MDXVI -1517 MDXVII -1518 MDXVIII -1519 MDXIX -1520 MDXX -1521 MDXXI -1522 MDXXII -1523 MDXXIII -1524 MDXXIV -1525 MDXXV -1526 MDXXVI -1527 MDXXVII -1528 MDXXVIII -1529 MDXXIX -1530 MDXXX -1531 MDXXXI -1532 MDXXXII -1533 MDXXXIII -1534 MDXXXIV -1535 MDXXXV -1536 MDXXXVI -1537 MDXXXVII -1538 MDXXXVIII -1539 MDXXXIX -1540 MDXL -1541 MDXLI -1542 MDXLII -1543 MDXLIII -1544 MDXLIV -1545 MDXLV -1546 MDXLVI -1547 MDXLVII -1548 MDXLVIII -1549 MDXLIX -1550 MDL -1551 MDLI -1552 MDLII -1553 MDLIII -1554 MDLIV -1555 MDLV -1556 MDLVI -1557 MDLVII -1558 MDLVIII -1559 MDLIX -1560 MDLX -1561 MDLXI -1562 MDLXII -1563 MDLXIII -1564 MDLXIV -1565 MDLXV -1566 MDLXVI -1567 MDLXVII -1568 MDLXVIII -1569 MDLXIX -1570 MDLXX -1571 MDLXXI -1572 MDLXXII -1573 MDLXXIII -1574 MDLXXIV -1575 MDLXXV -1576 MDLXXVI -1577 MDLXXVII -1578 MDLXXVIII -1579 MDLXXIX -1580 MDLXXX -1581 MDLXXXI -1582 MDLXXXII -1583 MDLXXXIII -1584 MDLXXXIV -1585 MDLXXXV -1586 MDLXXXVI -1587 MDLXXXVII -1588 MDLXXXVIII -1589 MDLXXXIX -1590 MDXC -1591 MDXCI -1592 MDXCII -1593 MDXCIII -1594 MDXCIV -1595 MDXCV -1596 MDXCVI -1597 MDXCVII -1598 MDXCVIII -1599 MDXCIX -1600 MDC -1601 MDCI -1602 MDCII -1603 MDCIII -1604 MDCIV -1605 MDCV -1606 MDCVI -1607 MDCVII -1608 MDCVIII -1609 MDCIX -1610 MDCX -1611 MDCXI -1612 MDCXII -1613 MDCXIII -1614 MDCXIV -1615 MDCXV -1616 MDCXVI -1617 MDCXVII -1618 MDCXVIII -1619 MDCXIX -1620 MDCXX -1621 MDCXXI -1622 MDCXXII -1623 MDCXXIII -1624 MDCXXIV -1625 MDCXXV -1626 MDCXXVI -1627 MDCXXVII -1628 MDCXXVIII -1629 MDCXXIX -1630 MDCXXX -1631 MDCXXXI -1632 MDCXXXII -1633 MDCXXXIII -1634 MDCXXXIV -1635 MDCXXXV -1636 MDCXXXVI -1637 MDCXXXVII -1638 MDCXXXVIII -1639 MDCXXXIX -1640 MDCXL -1641 MDCXLI -1642 MDCXLII -1643 MDCXLIII -1644 MDCXLIV -1645 MDCXLV -1646 MDCXLVI -1647 MDCXLVII -1648 MDCXLVIII -1649 MDCXLIX -1650 MDCL -1651 MDCLI -1652 MDCLII -1653 MDCLIII -1654 MDCLIV -1655 MDCLV -1656 MDCLVI -1657 MDCLVII -1658 MDCLVIII -1659 MDCLIX -1660 MDCLX -1661 MDCLXI -1662 MDCLXII -1663 MDCLXIII -1664 MDCLXIV -1665 MDCLXV -1666 MDCLXVI -1667 MDCLXVII -1668 MDCLXVIII -1669 MDCLXIX -1670 MDCLXX -1671 MDCLXXI -1672 MDCLXXII -1673 MDCLXXIII -1674 MDCLXXIV -1675 MDCLXXV -1676 MDCLXXVI -1677 MDCLXXVII -1678 MDCLXXVIII -1679 MDCLXXIX -1680 MDCLXXX -1681 MDCLXXXI -1682 MDCLXXXII -1683 MDCLXXXIII -1684 MDCLXXXIV -1685 MDCLXXXV -1686 MDCLXXXVI -1687 MDCLXXXVII -1688 MDCLXXXVIII -1689 MDCLXXXIX -1690 MDCXC -1691 MDCXCI -1692 MDCXCII -1693 MDCXCIII -1694 MDCXCIV -1695 MDCXCV -1696 MDCXCVI -1697 MDCXCVII -1698 MDCXCVIII -1699 MDCXCIX -1700 MDCC -1701 MDCCI -1702 MDCCII -1703 MDCCIII -1704 MDCCIV -1705 MDCCV -1706 MDCCVI -1707 MDCCVII -1708 MDCCVIII -1709 MDCCIX -1710 MDCCX -1711 MDCCXI -1712 MDCCXII -1713 MDCCXIII -1714 MDCCXIV -1715 MDCCXV -1716 MDCCXVI -1717 MDCCXVII -1718 MDCCXVIII -1719 MDCCXIX -1720 MDCCXX -1721 MDCCXXI -1722 MDCCXXII -1723 MDCCXXIII -1724 MDCCXXIV -1725 MDCCXXV -1726 MDCCXXVI -1727 MDCCXXVII -1728 MDCCXXVIII -1729 MDCCXXIX -1730 MDCCXXX -1731 MDCCXXXI -1732 MDCCXXXII -1733 MDCCXXXIII -1734 MDCCXXXIV -1735 MDCCXXXV -1736 MDCCXXXVI -1737 MDCCXXXVII -1738 MDCCXXXVIII -1739 MDCCXXXIX -1740 MDCCXL -1741 MDCCXLI -1742 MDCCXLII -1743 MDCCXLIII -1744 MDCCXLIV -1745 MDCCXLV -1746 MDCCXLVI -1747 MDCCXLVII -1748 MDCCXLVIII -1749 MDCCXLIX -1750 MDCCL -1751 MDCCLI -1752 MDCCLII -1753 MDCCLIII -1754 MDCCLIV -1755 MDCCLV -1756 MDCCLVI -1757 MDCCLVII -1758 MDCCLVIII -1759 MDCCLIX -1760 MDCCLX -1761 MDCCLXI -1762 MDCCLXII -1763 MDCCLXIII -1764 MDCCLXIV -1765 MDCCLXV -1766 MDCCLXVI -1767 MDCCLXVII -1768 MDCCLXVIII -1769 MDCCLXIX -1770 MDCCLXX -1771 MDCCLXXI -1772 MDCCLXXII -1773 MDCCLXXIII -1774 MDCCLXXIV -1775 MDCCLXXV -1776 MDCCLXXVI -1777 MDCCLXXVII -1778 MDCCLXXVIII -1779 MDCCLXXIX -1780 MDCCLXXX -1781 MDCCLXXXI -1782 MDCCLXXXII -1783 MDCCLXXXIII -1784 MDCCLXXXIV -1785 MDCCLXXXV -1786 MDCCLXXXVI -1787 MDCCLXXXVII -1788 MDCCLXXXVIII -1789 MDCCLXXXIX -1790 MDCCXC -1791 MDCCXCI -1792 MDCCXCII -1793 MDCCXCIII -1794 MDCCXCIV -1795 MDCCXCV -1796 MDCCXCVI -1797 MDCCXCVII -1798 MDCCXCVIII -1799 MDCCXCIX -1800 MDCCC -1801 MDCCCI -1802 MDCCCII -1803 MDCCCIII -1804 MDCCCIV -1805 MDCCCV -1806 MDCCCVI -1807 MDCCCVII -1808 MDCCCVIII -1809 MDCCCIX -1810 MDCCCX -1811 MDCCCXI -1812 MDCCCXII -1813 MDCCCXIII -1814 MDCCCXIV -1815 MDCCCXV -1816 MDCCCXVI -1817 MDCCCXVII -1818 MDCCCXVIII -1819 MDCCCXIX -1820 MDCCCXX -1821 MDCCCXXI -1822 MDCCCXXII -1823 MDCCCXXIII -1824 MDCCCXXIV -1825 MDCCCXXV -1826 MDCCCXXVI -1827 MDCCCXXVII -1828 MDCCCXXVIII -1829 MDCCCXXIX -1830 MDCCCXXX -1831 MDCCCXXXI -1832 MDCCCXXXII -1833 MDCCCXXXIII -1834 MDCCCXXXIV -1835 MDCCCXXXV -1836 MDCCCXXXVI -1837 MDCCCXXXVII -1838 MDCCCXXXVIII -1839 MDCCCXXXIX -1840 MDCCCXL -1841 MDCCCXLI -1842 MDCCCXLII -1843 MDCCCXLIII -1844 MDCCCXLIV -1845 MDCCCXLV -1846 MDCCCXLVI -1847 MDCCCXLVII -1848 MDCCCXLVIII -1849 MDCCCXLIX -1850 MDCCCL -1851 MDCCCLI -1852 MDCCCLII -1853 MDCCCLIII -1854 MDCCCLIV -1855 MDCCCLV -1856 MDCCCLVI -1857 MDCCCLVII -1858 MDCCCLVIII -1859 MDCCCLIX -1860 MDCCCLX -1861 MDCCCLXI -1862 MDCCCLXII -1863 MDCCCLXIII -1864 MDCCCLXIV -1865 MDCCCLXV -1866 MDCCCLXVI -1867 MDCCCLXVII -1868 MDCCCLXVIII -1869 MDCCCLXIX -1870 MDCCCLXX -1871 MDCCCLXXI -1872 MDCCCLXXII -1873 MDCCCLXXIII -1874 MDCCCLXXIV -1875 MDCCCLXXV -1876 MDCCCLXXVI -1877 MDCCCLXXVII -1878 MDCCCLXXVIII -1879 MDCCCLXXIX -1880 MDCCCLXXX -1881 MDCCCLXXXI -1882 MDCCCLXXXII -1883 MDCCCLXXXIII -1884 MDCCCLXXXIV -1885 MDCCCLXXXV -1886 MDCCCLXXXVI -1887 MDCCCLXXXVII -1888 MDCCCLXXXVIII -1889 MDCCCLXXXIX -1890 MDCCCXC -1891 MDCCCXCI -1892 MDCCCXCII -1893 MDCCCXCIII -1894 MDCCCXCIV -1895 MDCCCXCV -1896 MDCCCXCVI -1897 MDCCCXCVII -1898 MDCCCXCVIII -1899 MDCCCXCIX -1900 MCM -1901 MCMI -1902 MCMII -1903 MCMIII -1904 MCMIV -1905 MCMV -1906 MCMVI -1907 MCMVII -1908 MCMVIII -1909 MCMIX -1910 MCMX -1911 MCMXI -1912 MCMXII -1913 MCMXIII -1914 MCMXIV -1915 MCMXV -1916 MCMXVI -1917 MCMXVII -1918 MCMXVIII -1919 MCMXIX -1920 MCMXX -1921 MCMXXI -1922 MCMXXII -1923 MCMXXIII -1924 MCMXXIV -1925 MCMXXV -1926 MCMXXVI -1927 MCMXXVII -1928 MCMXXVIII -1929 MCMXXIX -1930 MCMXXX -1931 MCMXXXI -1932 MCMXXXII -1933 MCMXXXIII -1934 MCMXXXIV -1935 MCMXXXV -1936 MCMXXXVI -1937 MCMXXXVII -1938 MCMXXXVIII -1939 MCMXXXIX -1940 MCMXL -1941 MCMXLI -1942 MCMXLII -1943 MCMXLIII -1944 MCMXLIV -1945 MCMXLV -1946 MCMXLVI -1947 MCMXLVII -1948 MCMXLVIII -1949 MCMXLIX -1950 MCML -1951 MCMLI -1952 MCMLII -1953 MCMLIII -1954 MCMLIV -1955 MCMLV -1956 MCMLVI -1957 MCMLVII -1958 MCMLVIII -1959 MCMLIX -1960 MCMLX -1961 MCMLXI -1962 MCMLXII -1963 MCMLXIII -1964 MCMLXIV -1965 MCMLXV -1966 MCMLXVI -1967 MCMLXVII -1968 MCMLXVIII -1969 MCMLXIX -1970 MCMLXX -1971 MCMLXXI -1972 MCMLXXII -1973 MCMLXXIII -1974 MCMLXXIV -1975 MCMLXXV -1976 MCMLXXVI -1977 MCMLXXVII -1978 MCMLXXVIII -1979 MCMLXXIX -1980 MCMLXXX -1981 MCMLXXXI -1982 MCMLXXXII -1983 MCMLXXXIII -1984 MCMLXXXIV -1985 MCMLXXXV -1986 MCMLXXXVI -1987 MCMLXXXVII -1988 MCMLXXXVIII -1989 MCMLXXXIX -1990 MCMXC -1991 MCMXCI -1992 MCMXCII -1993 MCMXCIII -1994 MCMXCIV -1995 MCMXCV -1996 MCMXCVI -1997 MCMXCVII -1998 MCMXCVIII -1999 MCMXCIX -2000 MM -2001 MMI -2002 MMII -2003 MMIII -2004 MMIV -2005 MMV -2006 MMVI -2007 MMVII -2008 MMVIII -2009 MMIX -2010 MMX -2011 MMXI -2012 MMXII -2013 MMXIII -2014 MMXIV -2015 MMXV -2016 MMXVI -2017 MMXVII -2018 MMXVIII -2019 MMXIX -2020 MMXX -2021 MMXXI -2022 MMXXII -2023 MMXXIII -2024 MMXXIV -2025 MMXXV -2026 MMXXVI -2027 MMXXVII -2028 MMXXVIII -2029 MMXXIX -2030 MMXXX -2031 MMXXXI -2032 MMXXXII -2033 MMXXXIII -2034 MMXXXIV -2035 MMXXXV -2036 MMXXXVI -2037 MMXXXVII -2038 MMXXXVIII -2039 MMXXXIX -2040 MMXL -2041 MMXLI -2042 MMXLII -2043 MMXLIII -2044 MMXLIV -2045 MMXLV -2046 MMXLVI -2047 MMXLVII -2048 MMXLVIII -2049 MMXLIX -2050 MML -2051 MMLI -2052 MMLII -2053 MMLIII -2054 MMLIV -2055 MMLV -2056 MMLVI -2057 MMLVII -2058 MMLVIII -2059 MMLIX -2060 MMLX -2061 MMLXI -2062 MMLXII -2063 MMLXIII -2064 MMLXIV -2065 MMLXV -2066 MMLXVI -2067 MMLXVII -2068 MMLXVIII -2069 MMLXIX -2070 MMLXX -2071 MMLXXI -2072 MMLXXII -2073 MMLXXIII -2074 MMLXXIV -2075 MMLXXV -2076 MMLXXVI -2077 MMLXXVII -2078 MMLXXVIII -2079 MMLXXIX -2080 MMLXXX -2081 MMLXXXI -2082 MMLXXXII -2083 MMLXXXIII -2084 MMLXXXIV -2085 MMLXXXV -2086 MMLXXXVI -2087 MMLXXXVII -2088 MMLXXXVIII -2089 MMLXXXIX -2090 MMXC -2091 MMXCI -2092 MMXCII -2093 MMXCIII -2094 MMXCIV -2095 MMXCV -2096 MMXCVI -2097 MMXCVII -2098 MMXCVIII -2099 MMXCIX -2100 MMC -2101 MMCI -2102 MMCII -2103 MMCIII -2104 MMCIV -2105 MMCV -2106 MMCVI -2107 MMCVII -2108 MMCVIII -2109 MMCIX -2110 MMCX -2111 MMCXI -2112 MMCXII -2113 MMCXIII -2114 MMCXIV -2115 MMCXV -2116 MMCXVI -2117 MMCXVII -2118 MMCXVIII -2119 MMCXIX -2120 MMCXX -2121 MMCXXI -2122 MMCXXII -2123 MMCXXIII -2124 MMCXXIV -2125 MMCXXV -2126 MMCXXVI -2127 MMCXXVII -2128 MMCXXVIII -2129 MMCXXIX -2130 MMCXXX -2131 MMCXXXI -2132 MMCXXXII -2133 MMCXXXIII -2134 MMCXXXIV -2135 MMCXXXV -2136 MMCXXXVI -2137 MMCXXXVII -2138 MMCXXXVIII -2139 MMCXXXIX -2140 MMCXL -2141 MMCXLI -2142 MMCXLII -2143 MMCXLIII -2144 MMCXLIV -2145 MMCXLV -2146 MMCXLVI -2147 MMCXLVII -2148 MMCXLVIII -2149 MMCXLIX -2150 MMCL -2151 MMCLI -2152 MMCLII -2153 MMCLIII -2154 MMCLIV -2155 MMCLV -2156 MMCLVI -2157 MMCLVII -2158 MMCLVIII -2159 MMCLIX -2160 MMCLX -2161 MMCLXI -2162 MMCLXII -2163 MMCLXIII -2164 MMCLXIV -2165 MMCLXV -2166 MMCLXVI -2167 MMCLXVII -2168 MMCLXVIII -2169 MMCLXIX -2170 MMCLXX -2171 MMCLXXI -2172 MMCLXXII -2173 MMCLXXIII -2174 MMCLXXIV -2175 MMCLXXV -2176 MMCLXXVI -2177 MMCLXXVII -2178 MMCLXXVIII -2179 MMCLXXIX -2180 MMCLXXX -2181 MMCLXXXI -2182 MMCLXXXII -2183 MMCLXXXIII -2184 MMCLXXXIV -2185 MMCLXXXV -2186 MMCLXXXVI -2187 MMCLXXXVII -2188 MMCLXXXVIII -2189 MMCLXXXIX -2190 MMCXC -2191 MMCXCI -2192 MMCXCII -2193 MMCXCIII -2194 MMCXCIV -2195 MMCXCV -2196 MMCXCVI -2197 MMCXCVII -2198 MMCXCVIII -2199 MMCXCIX -2200 MMCC -2201 MMCCI -2202 MMCCII -2203 MMCCIII -2204 MMCCIV -2205 MMCCV -2206 MMCCVI -2207 MMCCVII -2208 MMCCVIII -2209 MMCCIX -2210 MMCCX -2211 MMCCXI -2212 MMCCXII -2213 MMCCXIII -2214 MMCCXIV -2215 MMCCXV -2216 MMCCXVI -2217 MMCCXVII -2218 MMCCXVIII -2219 MMCCXIX -2220 MMCCXX -2221 MMCCXXI -2222 MMCCXXII -2223 MMCCXXIII -2224 MMCCXXIV -2225 MMCCXXV -2226 MMCCXXVI -2227 MMCCXXVII -2228 MMCCXXVIII -2229 MMCCXXIX -2230 MMCCXXX -2231 MMCCXXXI -2232 MMCCXXXII -2233 MMCCXXXIII -2234 MMCCXXXIV -2235 MMCCXXXV -2236 MMCCXXXVI -2237 MMCCXXXVII -2238 MMCCXXXVIII -2239 MMCCXXXIX -2240 MMCCXL -2241 MMCCXLI -2242 MMCCXLII -2243 MMCCXLIII -2244 MMCCXLIV -2245 MMCCXLV -2246 MMCCXLVI -2247 MMCCXLVII -2248 MMCCXLVIII -2249 MMCCXLIX -2250 MMCCL -2251 MMCCLI -2252 MMCCLII -2253 MMCCLIII -2254 MMCCLIV -2255 MMCCLV -2256 MMCCLVI -2257 MMCCLVII -2258 MMCCLVIII -2259 MMCCLIX -2260 MMCCLX -2261 MMCCLXI -2262 MMCCLXII -2263 MMCCLXIII -2264 MMCCLXIV -2265 MMCCLXV -2266 MMCCLXVI -2267 MMCCLXVII -2268 MMCCLXVIII -2269 MMCCLXIX -2270 MMCCLXX -2271 MMCCLXXI -2272 MMCCLXXII -2273 MMCCLXXIII -2274 MMCCLXXIV -2275 MMCCLXXV -2276 MMCCLXXVI -2277 MMCCLXXVII -2278 MMCCLXXVIII -2279 MMCCLXXIX -2280 MMCCLXXX -2281 MMCCLXXXI -2282 MMCCLXXXII -2283 MMCCLXXXIII -2284 MMCCLXXXIV -2285 MMCCLXXXV -2286 MMCCLXXXVI -2287 MMCCLXXXVII -2288 MMCCLXXXVIII -2289 MMCCLXXXIX -2290 MMCCXC -2291 MMCCXCI -2292 MMCCXCII -2293 MMCCXCIII -2294 MMCCXCIV -2295 MMCCXCV -2296 MMCCXCVI -2297 MMCCXCVII -2298 MMCCXCVIII -2299 MMCCXCIX -2300 MMCCC -2301 MMCCCI -2302 MMCCCII -2303 MMCCCIII -2304 MMCCCIV -2305 MMCCCV -2306 MMCCCVI -2307 MMCCCVII -2308 MMCCCVIII -2309 MMCCCIX -2310 MMCCCX -2311 MMCCCXI -2312 MMCCCXII -2313 MMCCCXIII -2314 MMCCCXIV -2315 MMCCCXV -2316 MMCCCXVI -2317 MMCCCXVII -2318 MMCCCXVIII -2319 MMCCCXIX -2320 MMCCCXX -2321 MMCCCXXI -2322 MMCCCXXII -2323 MMCCCXXIII -2324 MMCCCXXIV -2325 MMCCCXXV -2326 MMCCCXXVI -2327 MMCCCXXVII -2328 MMCCCXXVIII -2329 MMCCCXXIX -2330 MMCCCXXX -2331 MMCCCXXXI -2332 MMCCCXXXII -2333 MMCCCXXXIII -2334 MMCCCXXXIV -2335 MMCCCXXXV -2336 MMCCCXXXVI -2337 MMCCCXXXVII -2338 MMCCCXXXVIII -2339 MMCCCXXXIX -2340 MMCCCXL -2341 MMCCCXLI -2342 MMCCCXLII -2343 MMCCCXLIII -2344 MMCCCXLIV -2345 MMCCCXLV -2346 MMCCCXLVI -2347 MMCCCXLVII -2348 MMCCCXLVIII -2349 MMCCCXLIX -2350 MMCCCL -2351 MMCCCLI -2352 MMCCCLII -2353 MMCCCLIII -2354 MMCCCLIV -2355 MMCCCLV -2356 MMCCCLVI -2357 MMCCCLVII -2358 MMCCCLVIII -2359 MMCCCLIX -2360 MMCCCLX -2361 MMCCCLXI -2362 MMCCCLXII -2363 MMCCCLXIII -2364 MMCCCLXIV -2365 MMCCCLXV -2366 MMCCCLXVI -2367 MMCCCLXVII -2368 MMCCCLXVIII -2369 MMCCCLXIX -2370 MMCCCLXX -2371 MMCCCLXXI -2372 MMCCCLXXII -2373 MMCCCLXXIII -2374 MMCCCLXXIV -2375 MMCCCLXXV -2376 MMCCCLXXVI -2377 MMCCCLXXVII -2378 MMCCCLXXVIII -2379 MMCCCLXXIX -2380 MMCCCLXXX -2381 MMCCCLXXXI -2382 MMCCCLXXXII -2383 MMCCCLXXXIII -2384 MMCCCLXXXIV -2385 MMCCCLXXXV -2386 MMCCCLXXXVI -2387 MMCCCLXXXVII -2388 MMCCCLXXXVIII -2389 MMCCCLXXXIX -2390 MMCCCXC -2391 MMCCCXCI -2392 MMCCCXCII -2393 MMCCCXCIII -2394 MMCCCXCIV -2395 MMCCCXCV -2396 MMCCCXCVI -2397 MMCCCXCVII -2398 MMCCCXCVIII -2399 MMCCCXCIX -2400 MMCD -2401 MMCDI -2402 MMCDII -2403 MMCDIII -2404 MMCDIV -2405 MMCDV -2406 MMCDVI -2407 MMCDVII -2408 MMCDVIII -2409 MMCDIX -2410 MMCDX -2411 MMCDXI -2412 MMCDXII -2413 MMCDXIII -2414 MMCDXIV -2415 MMCDXV -2416 MMCDXVI -2417 MMCDXVII -2418 MMCDXVIII -2419 MMCDXIX -2420 MMCDXX -2421 MMCDXXI -2422 MMCDXXII -2423 MMCDXXIII -2424 MMCDXXIV -2425 MMCDXXV -2426 MMCDXXVI -2427 MMCDXXVII -2428 MMCDXXVIII -2429 MMCDXXIX -2430 MMCDXXX -2431 MMCDXXXI -2432 MMCDXXXII -2433 MMCDXXXIII -2434 MMCDXXXIV -2435 MMCDXXXV -2436 MMCDXXXVI -2437 MMCDXXXVII -2438 MMCDXXXVIII -2439 MMCDXXXIX -2440 MMCDXL -2441 MMCDXLI -2442 MMCDXLII -2443 MMCDXLIII -2444 MMCDXLIV -2445 MMCDXLV -2446 MMCDXLVI -2447 MMCDXLVII -2448 MMCDXLVIII -2449 MMCDXLIX -2450 MMCDL -2451 MMCDLI -2452 MMCDLII -2453 MMCDLIII -2454 MMCDLIV -2455 MMCDLV -2456 MMCDLVI -2457 MMCDLVII -2458 MMCDLVIII -2459 MMCDLIX -2460 MMCDLX -2461 MMCDLXI -2462 MMCDLXII -2463 MMCDLXIII -2464 MMCDLXIV -2465 MMCDLXV -2466 MMCDLXVI -2467 MMCDLXVII -2468 MMCDLXVIII -2469 MMCDLXIX -2470 MMCDLXX -2471 MMCDLXXI -2472 MMCDLXXII -2473 MMCDLXXIII -2474 MMCDLXXIV -2475 MMCDLXXV -2476 MMCDLXXVI -2477 MMCDLXXVII -2478 MMCDLXXVIII -2479 MMCDLXXIX -2480 MMCDLXXX -2481 MMCDLXXXI -2482 MMCDLXXXII -2483 MMCDLXXXIII -2484 MMCDLXXXIV -2485 MMCDLXXXV -2486 MMCDLXXXVI -2487 MMCDLXXXVII -2488 MMCDLXXXVIII -2489 MMCDLXXXIX -2490 MMCDXC -2491 MMCDXCI -2492 MMCDXCII -2493 MMCDXCIII -2494 MMCDXCIV -2495 MMCDXCV -2496 MMCDXCVI -2497 MMCDXCVII -2498 MMCDXCVIII -2499 MMCDXCIX -2500 MMD -2501 MMDI -2502 MMDII -2503 MMDIII -2504 MMDIV -2505 MMDV -2506 MMDVI -2507 MMDVII -2508 MMDVIII -2509 MMDIX -2510 MMDX -2511 MMDXI -2512 MMDXII -2513 MMDXIII -2514 MMDXIV -2515 MMDXV -2516 MMDXVI -2517 MMDXVII -2518 MMDXVIII -2519 MMDXIX -2520 MMDXX -2521 MMDXXI -2522 MMDXXII -2523 MMDXXIII -2524 MMDXXIV -2525 MMDXXV -2526 MMDXXVI -2527 MMDXXVII -2528 MMDXXVIII -2529 MMDXXIX -2530 MMDXXX -2531 MMDXXXI -2532 MMDXXXII -2533 MMDXXXIII -2534 MMDXXXIV -2535 MMDXXXV -2536 MMDXXXVI -2537 MMDXXXVII -2538 MMDXXXVIII -2539 MMDXXXIX -2540 MMDXL -2541 MMDXLI -2542 MMDXLII -2543 MMDXLIII -2544 MMDXLIV -2545 MMDXLV -2546 MMDXLVI -2547 MMDXLVII -2548 MMDXLVIII -2549 MMDXLIX -2550 MMDL -2551 MMDLI -2552 MMDLII -2553 MMDLIII -2554 MMDLIV -2555 MMDLV -2556 MMDLVI -2557 MMDLVII -2558 MMDLVIII -2559 MMDLIX -2560 MMDLX -2561 MMDLXI -2562 MMDLXII -2563 MMDLXIII -2564 MMDLXIV -2565 MMDLXV -2566 MMDLXVI -2567 MMDLXVII -2568 MMDLXVIII -2569 MMDLXIX -2570 MMDLXX -2571 MMDLXXI -2572 MMDLXXII -2573 MMDLXXIII -2574 MMDLXXIV -2575 MMDLXXV -2576 MMDLXXVI -2577 MMDLXXVII -2578 MMDLXXVIII -2579 MMDLXXIX -2580 MMDLXXX -2581 MMDLXXXI -2582 MMDLXXXII -2583 MMDLXXXIII -2584 MMDLXXXIV -2585 MMDLXXXV -2586 MMDLXXXVI -2587 MMDLXXXVII -2588 MMDLXXXVIII -2589 MMDLXXXIX -2590 MMDXC -2591 MMDXCI -2592 MMDXCII -2593 MMDXCIII -2594 MMDXCIV -2595 MMDXCV -2596 MMDXCVI -2597 MMDXCVII -2598 MMDXCVIII -2599 MMDXCIX -2600 MMDC -2601 MMDCI -2602 MMDCII -2603 MMDCIII -2604 MMDCIV -2605 MMDCV -2606 MMDCVI -2607 MMDCVII -2608 MMDCVIII -2609 MMDCIX -2610 MMDCX -2611 MMDCXI -2612 MMDCXII -2613 MMDCXIII -2614 MMDCXIV -2615 MMDCXV -2616 MMDCXVI -2617 MMDCXVII -2618 MMDCXVIII -2619 MMDCXIX -2620 MMDCXX -2621 MMDCXXI -2622 MMDCXXII -2623 MMDCXXIII -2624 MMDCXXIV -2625 MMDCXXV -2626 MMDCXXVI -2627 MMDCXXVII -2628 MMDCXXVIII -2629 MMDCXXIX -2630 MMDCXXX -2631 MMDCXXXI -2632 MMDCXXXII -2633 MMDCXXXIII -2634 MMDCXXXIV -2635 MMDCXXXV -2636 MMDCXXXVI -2637 MMDCXXXVII -2638 MMDCXXXVIII -2639 MMDCXXXIX -2640 MMDCXL -2641 MMDCXLI -2642 MMDCXLII -2643 MMDCXLIII -2644 MMDCXLIV -2645 MMDCXLV -2646 MMDCXLVI -2647 MMDCXLVII -2648 MMDCXLVIII -2649 MMDCXLIX -2650 MMDCL -2651 MMDCLI -2652 MMDCLII -2653 MMDCLIII -2654 MMDCLIV -2655 MMDCLV -2656 MMDCLVI -2657 MMDCLVII -2658 MMDCLVIII -2659 MMDCLIX -2660 MMDCLX -2661 MMDCLXI -2662 MMDCLXII -2663 MMDCLXIII -2664 MMDCLXIV -2665 MMDCLXV -2666 MMDCLXVI -2667 MMDCLXVII -2668 MMDCLXVIII -2669 MMDCLXIX -2670 MMDCLXX -2671 MMDCLXXI -2672 MMDCLXXII -2673 MMDCLXXIII -2674 MMDCLXXIV -2675 MMDCLXXV -2676 MMDCLXXVI -2677 MMDCLXXVII -2678 MMDCLXXVIII -2679 MMDCLXXIX -2680 MMDCLXXX -2681 MMDCLXXXI -2682 MMDCLXXXII -2683 MMDCLXXXIII -2684 MMDCLXXXIV -2685 MMDCLXXXV -2686 MMDCLXXXVI -2687 MMDCLXXXVII -2688 MMDCLXXXVIII -2689 MMDCLXXXIX -2690 MMDCXC -2691 MMDCXCI -2692 MMDCXCII -2693 MMDCXCIII -2694 MMDCXCIV -2695 MMDCXCV -2696 MMDCXCVI -2697 MMDCXCVII -2698 MMDCXCVIII -2699 MMDCXCIX -2700 MMDCC -2701 MMDCCI -2702 MMDCCII -2703 MMDCCIII -2704 MMDCCIV -2705 MMDCCV -2706 MMDCCVI -2707 MMDCCVII -2708 MMDCCVIII -2709 MMDCCIX -2710 MMDCCX -2711 MMDCCXI -2712 MMDCCXII -2713 MMDCCXIII -2714 MMDCCXIV -2715 MMDCCXV -2716 MMDCCXVI -2717 MMDCCXVII -2718 MMDCCXVIII -2719 MMDCCXIX -2720 MMDCCXX -2721 MMDCCXXI -2722 MMDCCXXII -2723 MMDCCXXIII -2724 MMDCCXXIV -2725 MMDCCXXV -2726 MMDCCXXVI -2727 MMDCCXXVII -2728 MMDCCXXVIII -2729 MMDCCXXIX -2730 MMDCCXXX -2731 MMDCCXXXI -2732 MMDCCXXXII -2733 MMDCCXXXIII -2734 MMDCCXXXIV -2735 MMDCCXXXV -2736 MMDCCXXXVI -2737 MMDCCXXXVII -2738 MMDCCXXXVIII -2739 MMDCCXXXIX -2740 MMDCCXL -2741 MMDCCXLI -2742 MMDCCXLII -2743 MMDCCXLIII -2744 MMDCCXLIV -2745 MMDCCXLV -2746 MMDCCXLVI -2747 MMDCCXLVII -2748 MMDCCXLVIII -2749 MMDCCXLIX -2750 MMDCCL -2751 MMDCCLI -2752 MMDCCLII -2753 MMDCCLIII -2754 MMDCCLIV -2755 MMDCCLV -2756 MMDCCLVI -2757 MMDCCLVII -2758 MMDCCLVIII -2759 MMDCCLIX -2760 MMDCCLX -2761 MMDCCLXI -2762 MMDCCLXII -2763 MMDCCLXIII -2764 MMDCCLXIV -2765 MMDCCLXV -2766 MMDCCLXVI -2767 MMDCCLXVII -2768 MMDCCLXVIII -2769 MMDCCLXIX -2770 MMDCCLXX -2771 MMDCCLXXI -2772 MMDCCLXXII -2773 MMDCCLXXIII -2774 MMDCCLXXIV -2775 MMDCCLXXV -2776 MMDCCLXXVI -2777 MMDCCLXXVII -2778 MMDCCLXXVIII -2779 MMDCCLXXIX -2780 MMDCCLXXX -2781 MMDCCLXXXI -2782 MMDCCLXXXII -2783 MMDCCLXXXIII -2784 MMDCCLXXXIV -2785 MMDCCLXXXV -2786 MMDCCLXXXVI -2787 MMDCCLXXXVII -2788 MMDCCLXXXVIII -2789 MMDCCLXXXIX -2790 MMDCCXC -2791 MMDCCXCI -2792 MMDCCXCII -2793 MMDCCXCIII -2794 MMDCCXCIV -2795 MMDCCXCV -2796 MMDCCXCVI -2797 MMDCCXCVII -2798 MMDCCXCVIII -2799 MMDCCXCIX -2800 MMDCCC -2801 MMDCCCI -2802 MMDCCCII -2803 MMDCCCIII -2804 MMDCCCIV -2805 MMDCCCV -2806 MMDCCCVI -2807 MMDCCCVII -2808 MMDCCCVIII -2809 MMDCCCIX -2810 MMDCCCX -2811 MMDCCCXI -2812 MMDCCCXII -2813 MMDCCCXIII -2814 MMDCCCXIV -2815 MMDCCCXV -2816 MMDCCCXVI -2817 MMDCCCXVII -2818 MMDCCCXVIII -2819 MMDCCCXIX -2820 MMDCCCXX -2821 MMDCCCXXI -2822 MMDCCCXXII -2823 MMDCCCXXIII -2824 MMDCCCXXIV -2825 MMDCCCXXV -2826 MMDCCCXXVI -2827 MMDCCCXXVII -2828 MMDCCCXXVIII -2829 MMDCCCXXIX -2830 MMDCCCXXX -2831 MMDCCCXXXI -2832 MMDCCCXXXII -2833 MMDCCCXXXIII -2834 MMDCCCXXXIV -2835 MMDCCCXXXV -2836 MMDCCCXXXVI -2837 MMDCCCXXXVII -2838 MMDCCCXXXVIII -2839 MMDCCCXXXIX -2840 MMDCCCXL -2841 MMDCCCXLI -2842 MMDCCCXLII -2843 MMDCCCXLIII -2844 MMDCCCXLIV -2845 MMDCCCXLV -2846 MMDCCCXLVI -2847 MMDCCCXLVII -2848 MMDCCCXLVIII -2849 MMDCCCXLIX -2850 MMDCCCL -2851 MMDCCCLI -2852 MMDCCCLII -2853 MMDCCCLIII -2854 MMDCCCLIV -2855 MMDCCCLV -2856 MMDCCCLVI -2857 MMDCCCLVII -2858 MMDCCCLVIII -2859 MMDCCCLIX -2860 MMDCCCLX -2861 MMDCCCLXI -2862 MMDCCCLXII -2863 MMDCCCLXIII -2864 MMDCCCLXIV -2865 MMDCCCLXV -2866 MMDCCCLXVI -2867 MMDCCCLXVII -2868 MMDCCCLXVIII -2869 MMDCCCLXIX -2870 MMDCCCLXX -2871 MMDCCCLXXI -2872 MMDCCCLXXII -2873 MMDCCCLXXIII -2874 MMDCCCLXXIV -2875 MMDCCCLXXV -2876 MMDCCCLXXVI -2877 MMDCCCLXXVII -2878 MMDCCCLXXVIII -2879 MMDCCCLXXIX -2880 MMDCCCLXXX -2881 MMDCCCLXXXI -2882 MMDCCCLXXXII -2883 MMDCCCLXXXIII -2884 MMDCCCLXXXIV -2885 MMDCCCLXXXV -2886 MMDCCCLXXXVI -2887 MMDCCCLXXXVII -2888 MMDCCCLXXXVIII -2889 MMDCCCLXXXIX -2890 MMDCCCXC -2891 MMDCCCXCI -2892 MMDCCCXCII -2893 MMDCCCXCIII -2894 MMDCCCXCIV -2895 MMDCCCXCV -2896 MMDCCCXCVI -2897 MMDCCCXCVII -2898 MMDCCCXCVIII -2899 MMDCCCXCIX -2900 MMCM -2901 MMCMI -2902 MMCMII -2903 MMCMIII -2904 MMCMIV -2905 MMCMV -2906 MMCMVI -2907 MMCMVII -2908 MMCMVIII -2909 MMCMIX -2910 MMCMX -2911 MMCMXI -2912 MMCMXII -2913 MMCMXIII -2914 MMCMXIV -2915 MMCMXV -2916 MMCMXVI -2917 MMCMXVII -2918 MMCMXVIII -2919 MMCMXIX -2920 MMCMXX -2921 MMCMXXI -2922 MMCMXXII -2923 MMCMXXIII -2924 MMCMXXIV -2925 MMCMXXV -2926 MMCMXXVI -2927 MMCMXXVII -2928 MMCMXXVIII -2929 MMCMXXIX -2930 MMCMXXX -2931 MMCMXXXI -2932 MMCMXXXII -2933 MMCMXXXIII -2934 MMCMXXXIV -2935 MMCMXXXV -2936 MMCMXXXVI -2937 MMCMXXXVII -2938 MMCMXXXVIII -2939 MMCMXXXIX -2940 MMCMXL -2941 MMCMXLI -2942 MMCMXLII -2943 MMCMXLIII -2944 MMCMXLIV -2945 MMCMXLV -2946 MMCMXLVI -2947 MMCMXLVII -2948 MMCMXLVIII -2949 MMCMXLIX -2950 MMCML -2951 MMCMLI -2952 MMCMLII -2953 MMCMLIII -2954 MMCMLIV -2955 MMCMLV -2956 MMCMLVI -2957 MMCMLVII -2958 MMCMLVIII -2959 MMCMLIX -2960 MMCMLX -2961 MMCMLXI -2962 MMCMLXII -2963 MMCMLXIII -2964 MMCMLXIV -2965 MMCMLXV -2966 MMCMLXVI -2967 MMCMLXVII -2968 MMCMLXVIII -2969 MMCMLXIX -2970 MMCMLXX -2971 MMCMLXXI -2972 MMCMLXXII -2973 MMCMLXXIII -2974 MMCMLXXIV -2975 MMCMLXXV -2976 MMCMLXXVI -2977 MMCMLXXVII -2978 MMCMLXXVIII -2979 MMCMLXXIX -2980 MMCMLXXX -2981 MMCMLXXXI -2982 MMCMLXXXII -2983 MMCMLXXXIII -2984 MMCMLXXXIV -2985 MMCMLXXXV -2986 MMCMLXXXVI -2987 MMCMLXXXVII -2988 MMCMLXXXVIII -2989 MMCMLXXXIX -2990 MMCMXC -2991 MMCMXCI -2992 MMCMXCII -2993 MMCMXCIII -2994 MMCMXCIV -2995 MMCMXCV -2996 MMCMXCVI -2997 MMCMXCVII -2998 MMCMXCVIII -2999 MMCMXCIX -3000 MMM -3001 MMMI -3002 MMMII -3003 MMMIII -3004 MMMIV -3005 MMMV -3006 MMMVI -3007 MMMVII -3008 MMMVIII -3009 MMMIX -3010 MMMX -3011 MMMXI -3012 MMMXII -3013 MMMXIII -3014 MMMXIV -3015 MMMXV -3016 MMMXVI -3017 MMMXVII -3018 MMMXVIII -3019 MMMXIX -3020 MMMXX -3021 MMMXXI -3022 MMMXXII -3023 MMMXXIII -3024 MMMXXIV -3025 MMMXXV -3026 MMMXXVI -3027 MMMXXVII -3028 MMMXXVIII -3029 MMMXXIX -3030 MMMXXX -3031 MMMXXXI -3032 MMMXXXII -3033 MMMXXXIII -3034 MMMXXXIV -3035 MMMXXXV -3036 MMMXXXVI -3037 MMMXXXVII -3038 MMMXXXVIII -3039 MMMXXXIX -3040 MMMXL -3041 MMMXLI -3042 MMMXLII -3043 MMMXLIII -3044 MMMXLIV -3045 MMMXLV -3046 MMMXLVI -3047 MMMXLVII -3048 MMMXLVIII -3049 MMMXLIX -3050 MMML -3051 MMMLI -3052 MMMLII -3053 MMMLIII -3054 MMMLIV -3055 MMMLV -3056 MMMLVI -3057 MMMLVII -3058 MMMLVIII -3059 MMMLIX -3060 MMMLX -3061 MMMLXI -3062 MMMLXII -3063 MMMLXIII -3064 MMMLXIV -3065 MMMLXV -3066 MMMLXVI -3067 MMMLXVII -3068 MMMLXVIII -3069 MMMLXIX -3070 MMMLXX -3071 MMMLXXI -3072 MMMLXXII -3073 MMMLXXIII -3074 MMMLXXIV -3075 MMMLXXV -3076 MMMLXXVI -3077 MMMLXXVII -3078 MMMLXXVIII -3079 MMMLXXIX -3080 MMMLXXX -3081 MMMLXXXI -3082 MMMLXXXII -3083 MMMLXXXIII -3084 MMMLXXXIV -3085 MMMLXXXV -3086 MMMLXXXVI -3087 MMMLXXXVII -3088 MMMLXXXVIII -3089 MMMLXXXIX -3090 MMMXC -3091 MMMXCI -3092 MMMXCII -3093 MMMXCIII -3094 MMMXCIV -3095 MMMXCV -3096 MMMXCVI -3097 MMMXCVII -3098 MMMXCVIII -3099 MMMXCIX -3100 MMMC -3101 MMMCI -3102 MMMCII -3103 MMMCIII -3104 MMMCIV -3105 MMMCV -3106 MMMCVI -3107 MMMCVII -3108 MMMCVIII -3109 MMMCIX -3110 MMMCX -3111 MMMCXI -3112 MMMCXII -3113 MMMCXIII -3114 MMMCXIV -3115 MMMCXV -3116 MMMCXVI -3117 MMMCXVII -3118 MMMCXVIII -3119 MMMCXIX -3120 MMMCXX -3121 MMMCXXI -3122 MMMCXXII -3123 MMMCXXIII -3124 MMMCXXIV -3125 MMMCXXV -3126 MMMCXXVI -3127 MMMCXXVII -3128 MMMCXXVIII -3129 MMMCXXIX -3130 MMMCXXX -3131 MMMCXXXI -3132 MMMCXXXII -3133 MMMCXXXIII -3134 MMMCXXXIV -3135 MMMCXXXV -3136 MMMCXXXVI -3137 MMMCXXXVII -3138 MMMCXXXVIII -3139 MMMCXXXIX -3140 MMMCXL -3141 MMMCXLI -3142 MMMCXLII -3143 MMMCXLIII -3144 MMMCXLIV -3145 MMMCXLV -3146 MMMCXLVI -3147 MMMCXLVII -3148 MMMCXLVIII -3149 MMMCXLIX -3150 MMMCL -3151 MMMCLI -3152 MMMCLII -3153 MMMCLIII -3154 MMMCLIV -3155 MMMCLV -3156 MMMCLVI -3157 MMMCLVII -3158 MMMCLVIII -3159 MMMCLIX -3160 MMMCLX -3161 MMMCLXI -3162 MMMCLXII -3163 MMMCLXIII -3164 MMMCLXIV -3165 MMMCLXV -3166 MMMCLXVI -3167 MMMCLXVII -3168 MMMCLXVIII -3169 MMMCLXIX -3170 MMMCLXX -3171 MMMCLXXI -3172 MMMCLXXII -3173 MMMCLXXIII -3174 MMMCLXXIV -3175 MMMCLXXV -3176 MMMCLXXVI -3177 MMMCLXXVII -3178 MMMCLXXVIII -3179 MMMCLXXIX -3180 MMMCLXXX -3181 MMMCLXXXI -3182 MMMCLXXXII -3183 MMMCLXXXIII -3184 MMMCLXXXIV -3185 MMMCLXXXV -3186 MMMCLXXXVI -3187 MMMCLXXXVII -3188 MMMCLXXXVIII -3189 MMMCLXXXIX -3190 MMMCXC -3191 MMMCXCI -3192 MMMCXCII -3193 MMMCXCIII -3194 MMMCXCIV -3195 MMMCXCV -3196 MMMCXCVI -3197 MMMCXCVII -3198 MMMCXCVIII -3199 MMMCXCIX -3200 MMMCC -3201 MMMCCI -3202 MMMCCII -3203 MMMCCIII -3204 MMMCCIV -3205 MMMCCV -3206 MMMCCVI -3207 MMMCCVII -3208 MMMCCVIII -3209 MMMCCIX -3210 MMMCCX -3211 MMMCCXI -3212 MMMCCXII -3213 MMMCCXIII -3214 MMMCCXIV -3215 MMMCCXV -3216 MMMCCXVI -3217 MMMCCXVII -3218 MMMCCXVIII -3219 MMMCCXIX -3220 MMMCCXX -3221 MMMCCXXI -3222 MMMCCXXII -3223 MMMCCXXIII -3224 MMMCCXXIV -3225 MMMCCXXV -3226 MMMCCXXVI -3227 MMMCCXXVII -3228 MMMCCXXVIII -3229 MMMCCXXIX -3230 MMMCCXXX -3231 MMMCCXXXI -3232 MMMCCXXXII -3233 MMMCCXXXIII -3234 MMMCCXXXIV -3235 MMMCCXXXV -3236 MMMCCXXXVI -3237 MMMCCXXXVII -3238 MMMCCXXXVIII -3239 MMMCCXXXIX -3240 MMMCCXL -3241 MMMCCXLI -3242 MMMCCXLII -3243 MMMCCXLIII -3244 MMMCCXLIV -3245 MMMCCXLV -3246 MMMCCXLVI -3247 MMMCCXLVII -3248 MMMCCXLVIII -3249 MMMCCXLIX -3250 MMMCCL -3251 MMMCCLI -3252 MMMCCLII -3253 MMMCCLIII -3254 MMMCCLIV -3255 MMMCCLV -3256 MMMCCLVI -3257 MMMCCLVII -3258 MMMCCLVIII -3259 MMMCCLIX -3260 MMMCCLX -3261 MMMCCLXI -3262 MMMCCLXII -3263 MMMCCLXIII -3264 MMMCCLXIV -3265 MMMCCLXV -3266 MMMCCLXVI -3267 MMMCCLXVII -3268 MMMCCLXVIII -3269 MMMCCLXIX -3270 MMMCCLXX -3271 MMMCCLXXI -3272 MMMCCLXXII -3273 MMMCCLXXIII -3274 MMMCCLXXIV -3275 MMMCCLXXV -3276 MMMCCLXXVI -3277 MMMCCLXXVII -3278 MMMCCLXXVIII -3279 MMMCCLXXIX -3280 MMMCCLXXX -3281 MMMCCLXXXI -3282 MMMCCLXXXII -3283 MMMCCLXXXIII -3284 MMMCCLXXXIV -3285 MMMCCLXXXV -3286 MMMCCLXXXVI -3287 MMMCCLXXXVII -3288 MMMCCLXXXVIII -3289 MMMCCLXXXIX -3290 MMMCCXC -3291 MMMCCXCI -3292 MMMCCXCII -3293 MMMCCXCIII -3294 MMMCCXCIV -3295 MMMCCXCV -3296 MMMCCXCVI -3297 MMMCCXCVII -3298 MMMCCXCVIII -3299 MMMCCXCIX -3300 MMMCCC -3301 MMMCCCI -3302 MMMCCCII -3303 MMMCCCIII -3304 MMMCCCIV -3305 MMMCCCV -3306 MMMCCCVI -3307 MMMCCCVII -3308 MMMCCCVIII -3309 MMMCCCIX -3310 MMMCCCX -3311 MMMCCCXI -3312 MMMCCCXII -3313 MMMCCCXIII -3314 MMMCCCXIV -3315 MMMCCCXV -3316 MMMCCCXVI -3317 MMMCCCXVII -3318 MMMCCCXVIII -3319 MMMCCCXIX -3320 MMMCCCXX -3321 MMMCCCXXI -3322 MMMCCCXXII -3323 MMMCCCXXIII -3324 MMMCCCXXIV -3325 MMMCCCXXV -3326 MMMCCCXXVI -3327 MMMCCCXXVII -3328 MMMCCCXXVIII -3329 MMMCCCXXIX -3330 MMMCCCXXX -3331 MMMCCCXXXI -3332 MMMCCCXXXII -3333 MMMCCCXXXIII -3334 MMMCCCXXXIV -3335 MMMCCCXXXV -3336 MMMCCCXXXVI -3337 MMMCCCXXXVII -3338 MMMCCCXXXVIII -3339 MMMCCCXXXIX -3340 MMMCCCXL -3341 MMMCCCXLI -3342 MMMCCCXLII -3343 MMMCCCXLIII -3344 MMMCCCXLIV -3345 MMMCCCXLV -3346 MMMCCCXLVI -3347 MMMCCCXLVII -3348 MMMCCCXLVIII -3349 MMMCCCXLIX -3350 MMMCCCL -3351 MMMCCCLI -3352 MMMCCCLII -3353 MMMCCCLIII -3354 MMMCCCLIV -3355 MMMCCCLV -3356 MMMCCCLVI -3357 MMMCCCLVII -3358 MMMCCCLVIII -3359 MMMCCCLIX -3360 MMMCCCLX -3361 MMMCCCLXI -3362 MMMCCCLXII -3363 MMMCCCLXIII -3364 MMMCCCLXIV -3365 MMMCCCLXV -3366 MMMCCCLXVI -3367 MMMCCCLXVII -3368 MMMCCCLXVIII -3369 MMMCCCLXIX -3370 MMMCCCLXX -3371 MMMCCCLXXI -3372 MMMCCCLXXII -3373 MMMCCCLXXIII -3374 MMMCCCLXXIV -3375 MMMCCCLXXV -3376 MMMCCCLXXVI -3377 MMMCCCLXXVII -3378 MMMCCCLXXVIII -3379 MMMCCCLXXIX -3380 MMMCCCLXXX -3381 MMMCCCLXXXI -3382 MMMCCCLXXXII -3383 MMMCCCLXXXIII -3384 MMMCCCLXXXIV -3385 MMMCCCLXXXV -3386 MMMCCCLXXXVI -3387 MMMCCCLXXXVII -3388 MMMCCCLXXXVIII -3389 MMMCCCLXXXIX -3390 MMMCCCXC -3391 MMMCCCXCI -3392 MMMCCCXCII -3393 MMMCCCXCIII -3394 MMMCCCXCIV -3395 MMMCCCXCV -3396 MMMCCCXCVI -3397 MMMCCCXCVII -3398 MMMCCCXCVIII -3399 MMMCCCXCIX -3400 MMMCD -3401 MMMCDI -3402 MMMCDII -3403 MMMCDIII -3404 MMMCDIV -3405 MMMCDV -3406 MMMCDVI -3407 MMMCDVII -3408 MMMCDVIII -3409 MMMCDIX -3410 MMMCDX -3411 MMMCDXI -3412 MMMCDXII -3413 MMMCDXIII -3414 MMMCDXIV -3415 MMMCDXV -3416 MMMCDXVI -3417 MMMCDXVII -3418 MMMCDXVIII -3419 MMMCDXIX -3420 MMMCDXX -3421 MMMCDXXI -3422 MMMCDXXII -3423 MMMCDXXIII -3424 MMMCDXXIV -3425 MMMCDXXV -3426 MMMCDXXVI -3427 MMMCDXXVII -3428 MMMCDXXVIII -3429 MMMCDXXIX -3430 MMMCDXXX -3431 MMMCDXXXI -3432 MMMCDXXXII -3433 MMMCDXXXIII -3434 MMMCDXXXIV -3435 MMMCDXXXV -3436 MMMCDXXXVI -3437 MMMCDXXXVII -3438 MMMCDXXXVIII -3439 MMMCDXXXIX -3440 MMMCDXL -3441 MMMCDXLI -3442 MMMCDXLII -3443 MMMCDXLIII -3444 MMMCDXLIV -3445 MMMCDXLV -3446 MMMCDXLVI -3447 MMMCDXLVII -3448 MMMCDXLVIII -3449 MMMCDXLIX -3450 MMMCDL -3451 MMMCDLI -3452 MMMCDLII -3453 MMMCDLIII -3454 MMMCDLIV -3455 MMMCDLV -3456 MMMCDLVI -3457 MMMCDLVII -3458 MMMCDLVIII -3459 MMMCDLIX -3460 MMMCDLX -3461 MMMCDLXI -3462 MMMCDLXII -3463 MMMCDLXIII -3464 MMMCDLXIV -3465 MMMCDLXV -3466 MMMCDLXVI -3467 MMMCDLXVII -3468 MMMCDLXVIII -3469 MMMCDLXIX -3470 MMMCDLXX -3471 MMMCDLXXI -3472 MMMCDLXXII -3473 MMMCDLXXIII -3474 MMMCDLXXIV -3475 MMMCDLXXV -3476 MMMCDLXXVI -3477 MMMCDLXXVII -3478 MMMCDLXXVIII -3479 MMMCDLXXIX -3480 MMMCDLXXX -3481 MMMCDLXXXI -3482 MMMCDLXXXII -3483 MMMCDLXXXIII -3484 MMMCDLXXXIV -3485 MMMCDLXXXV -3486 MMMCDLXXXVI -3487 MMMCDLXXXVII -3488 MMMCDLXXXVIII -3489 MMMCDLXXXIX -3490 MMMCDXC -3491 MMMCDXCI -3492 MMMCDXCII -3493 MMMCDXCIII -3494 MMMCDXCIV -3495 MMMCDXCV -3496 MMMCDXCVI -3497 MMMCDXCVII -3498 MMMCDXCVIII -3499 MMMCDXCIX -3500 MMMD -3501 MMMDI -3502 MMMDII -3503 MMMDIII -3504 MMMDIV -3505 MMMDV -3506 MMMDVI -3507 MMMDVII -3508 MMMDVIII -3509 MMMDIX -3510 MMMDX -3511 MMMDXI -3512 MMMDXII -3513 MMMDXIII -3514 MMMDXIV -3515 MMMDXV -3516 MMMDXVI -3517 MMMDXVII -3518 MMMDXVIII -3519 MMMDXIX -3520 MMMDXX -3521 MMMDXXI -3522 MMMDXXII -3523 MMMDXXIII -3524 MMMDXXIV -3525 MMMDXXV -3526 MMMDXXVI -3527 MMMDXXVII -3528 MMMDXXVIII -3529 MMMDXXIX -3530 MMMDXXX -3531 MMMDXXXI -3532 MMMDXXXII -3533 MMMDXXXIII -3534 MMMDXXXIV -3535 MMMDXXXV -3536 MMMDXXXVI -3537 MMMDXXXVII -3538 MMMDXXXVIII -3539 MMMDXXXIX -3540 MMMDXL -3541 MMMDXLI -3542 MMMDXLII -3543 MMMDXLIII -3544 MMMDXLIV -3545 MMMDXLV -3546 MMMDXLVI -3547 MMMDXLVII -3548 MMMDXLVIII -3549 MMMDXLIX -3550 MMMDL -3551 MMMDLI -3552 MMMDLII -3553 MMMDLIII -3554 MMMDLIV -3555 MMMDLV -3556 MMMDLVI -3557 MMMDLVII -3558 MMMDLVIII -3559 MMMDLIX -3560 MMMDLX -3561 MMMDLXI -3562 MMMDLXII -3563 MMMDLXIII -3564 MMMDLXIV -3565 MMMDLXV -3566 MMMDLXVI -3567 MMMDLXVII -3568 MMMDLXVIII -3569 MMMDLXIX -3570 MMMDLXX -3571 MMMDLXXI -3572 MMMDLXXII -3573 MMMDLXXIII -3574 MMMDLXXIV -3575 MMMDLXXV -3576 MMMDLXXVI -3577 MMMDLXXVII -3578 MMMDLXXVIII -3579 MMMDLXXIX -3580 MMMDLXXX -3581 MMMDLXXXI -3582 MMMDLXXXII -3583 MMMDLXXXIII -3584 MMMDLXXXIV -3585 MMMDLXXXV -3586 MMMDLXXXVI -3587 MMMDLXXXVII -3588 MMMDLXXXVIII -3589 MMMDLXXXIX -3590 MMMDXC -3591 MMMDXCI -3592 MMMDXCII -3593 MMMDXCIII -3594 MMMDXCIV -3595 MMMDXCV -3596 MMMDXCVI -3597 MMMDXCVII -3598 MMMDXCVIII -3599 MMMDXCIX -3600 MMMDC -3601 MMMDCI -3602 MMMDCII -3603 MMMDCIII -3604 MMMDCIV -3605 MMMDCV -3606 MMMDCVI -3607 MMMDCVII -3608 MMMDCVIII -3609 MMMDCIX -3610 MMMDCX -3611 MMMDCXI -3612 MMMDCXII -3613 MMMDCXIII -3614 MMMDCXIV -3615 MMMDCXV -3616 MMMDCXVI -3617 MMMDCXVII -3618 MMMDCXVIII -3619 MMMDCXIX -3620 MMMDCXX -3621 MMMDCXXI -3622 MMMDCXXII -3623 MMMDCXXIII -3624 MMMDCXXIV -3625 MMMDCXXV -3626 MMMDCXXVI -3627 MMMDCXXVII -3628 MMMDCXXVIII -3629 MMMDCXXIX -3630 MMMDCXXX -3631 MMMDCXXXI -3632 MMMDCXXXII -3633 MMMDCXXXIII -3634 MMMDCXXXIV -3635 MMMDCXXXV -3636 MMMDCXXXVI -3637 MMMDCXXXVII -3638 MMMDCXXXVIII -3639 MMMDCXXXIX -3640 MMMDCXL -3641 MMMDCXLI -3642 MMMDCXLII -3643 MMMDCXLIII -3644 MMMDCXLIV -3645 MMMDCXLV -3646 MMMDCXLVI -3647 MMMDCXLVII -3648 MMMDCXLVIII -3649 MMMDCXLIX -3650 MMMDCL -3651 MMMDCLI -3652 MMMDCLII -3653 MMMDCLIII -3654 MMMDCLIV -3655 MMMDCLV -3656 MMMDCLVI -3657 MMMDCLVII -3658 MMMDCLVIII -3659 MMMDCLIX -3660 MMMDCLX -3661 MMMDCLXI -3662 MMMDCLXII -3663 MMMDCLXIII -3664 MMMDCLXIV -3665 MMMDCLXV -3666 MMMDCLXVI -3667 MMMDCLXVII -3668 MMMDCLXVIII -3669 MMMDCLXIX -3670 MMMDCLXX -3671 MMMDCLXXI -3672 MMMDCLXXII -3673 MMMDCLXXIII -3674 MMMDCLXXIV -3675 MMMDCLXXV -3676 MMMDCLXXVI -3677 MMMDCLXXVII -3678 MMMDCLXXVIII -3679 MMMDCLXXIX -3680 MMMDCLXXX -3681 MMMDCLXXXI -3682 MMMDCLXXXII -3683 MMMDCLXXXIII -3684 MMMDCLXXXIV -3685 MMMDCLXXXV -3686 MMMDCLXXXVI -3687 MMMDCLXXXVII -3688 MMMDCLXXXVIII -3689 MMMDCLXXXIX -3690 MMMDCXC -3691 MMMDCXCI -3692 MMMDCXCII -3693 MMMDCXCIII -3694 MMMDCXCIV -3695 MMMDCXCV -3696 MMMDCXCVI -3697 MMMDCXCVII -3698 MMMDCXCVIII -3699 MMMDCXCIX -3700 MMMDCC -3701 MMMDCCI -3702 MMMDCCII -3703 MMMDCCIII -3704 MMMDCCIV -3705 MMMDCCV -3706 MMMDCCVI -3707 MMMDCCVII -3708 MMMDCCVIII -3709 MMMDCCIX -3710 MMMDCCX -3711 MMMDCCXI -3712 MMMDCCXII -3713 MMMDCCXIII -3714 MMMDCCXIV -3715 MMMDCCXV -3716 MMMDCCXVI -3717 MMMDCCXVII -3718 MMMDCCXVIII -3719 MMMDCCXIX -3720 MMMDCCXX -3721 MMMDCCXXI -3722 MMMDCCXXII -3723 MMMDCCXXIII -3724 MMMDCCXXIV -3725 MMMDCCXXV -3726 MMMDCCXXVI -3727 MMMDCCXXVII -3728 MMMDCCXXVIII -3729 MMMDCCXXIX -3730 MMMDCCXXX -3731 MMMDCCXXXI -3732 MMMDCCXXXII -3733 MMMDCCXXXIII -3734 MMMDCCXXXIV -3735 MMMDCCXXXV -3736 MMMDCCXXXVI -3737 MMMDCCXXXVII -3738 MMMDCCXXXVIII -3739 MMMDCCXXXIX -3740 MMMDCCXL -3741 MMMDCCXLI -3742 MMMDCCXLII -3743 MMMDCCXLIII -3744 MMMDCCXLIV -3745 MMMDCCXLV -3746 MMMDCCXLVI -3747 MMMDCCXLVII -3748 MMMDCCXLVIII -3749 MMMDCCXLIX -3750 MMMDCCL -3751 MMMDCCLI -3752 MMMDCCLII -3753 MMMDCCLIII -3754 MMMDCCLIV -3755 MMMDCCLV -3756 MMMDCCLVI -3757 MMMDCCLVII -3758 MMMDCCLVIII -3759 MMMDCCLIX -3760 MMMDCCLX -3761 MMMDCCLXI -3762 MMMDCCLXII -3763 MMMDCCLXIII -3764 MMMDCCLXIV -3765 MMMDCCLXV -3766 MMMDCCLXVI -3767 MMMDCCLXVII -3768 MMMDCCLXVIII -3769 MMMDCCLXIX -3770 MMMDCCLXX -3771 MMMDCCLXXI -3772 MMMDCCLXXII -3773 MMMDCCLXXIII -3774 MMMDCCLXXIV -3775 MMMDCCLXXV -3776 MMMDCCLXXVI -3777 MMMDCCLXXVII -3778 MMMDCCLXXVIII -3779 MMMDCCLXXIX -3780 MMMDCCLXXX -3781 MMMDCCLXXXI -3782 MMMDCCLXXXII -3783 MMMDCCLXXXIII -3784 MMMDCCLXXXIV -3785 MMMDCCLXXXV -3786 MMMDCCLXXXVI -3787 MMMDCCLXXXVII -3788 MMMDCCLXXXVIII -3789 MMMDCCLXXXIX -3790 MMMDCCXC -3791 MMMDCCXCI -3792 MMMDCCXCII -3793 MMMDCCXCIII -3794 MMMDCCXCIV -3795 MMMDCCXCV -3796 MMMDCCXCVI -3797 MMMDCCXCVII -3798 MMMDCCXCVIII -3799 MMMDCCXCIX -3800 MMMDCCC -3801 MMMDCCCI -3802 MMMDCCCII -3803 MMMDCCCIII -3804 MMMDCCCIV -3805 MMMDCCCV -3806 MMMDCCCVI -3807 MMMDCCCVII -3808 MMMDCCCVIII -3809 MMMDCCCIX -3810 MMMDCCCX -3811 MMMDCCCXI -3812 MMMDCCCXII -3813 MMMDCCCXIII -3814 MMMDCCCXIV -3815 MMMDCCCXV -3816 MMMDCCCXVI -3817 MMMDCCCXVII -3818 MMMDCCCXVIII -3819 MMMDCCCXIX -3820 MMMDCCCXX -3821 MMMDCCCXXI -3822 MMMDCCCXXII -3823 MMMDCCCXXIII -3824 MMMDCCCXXIV -3825 MMMDCCCXXV -3826 MMMDCCCXXVI -3827 MMMDCCCXXVII -3828 MMMDCCCXXVIII -3829 MMMDCCCXXIX -3830 MMMDCCCXXX -3831 MMMDCCCXXXI -3832 MMMDCCCXXXII -3833 MMMDCCCXXXIII -3834 MMMDCCCXXXIV -3835 MMMDCCCXXXV -3836 MMMDCCCXXXVI -3837 MMMDCCCXXXVII -3838 MMMDCCCXXXVIII -3839 MMMDCCCXXXIX -3840 MMMDCCCXL -3841 MMMDCCCXLI -3842 MMMDCCCXLII -3843 MMMDCCCXLIII -3844 MMMDCCCXLIV -3845 MMMDCCCXLV -3846 MMMDCCCXLVI -3847 MMMDCCCXLVII -3848 MMMDCCCXLVIII -3849 MMMDCCCXLIX -3850 MMMDCCCL -3851 MMMDCCCLI -3852 MMMDCCCLII -3853 MMMDCCCLIII -3854 MMMDCCCLIV -3855 MMMDCCCLV -3856 MMMDCCCLVI -3857 MMMDCCCLVII -3858 MMMDCCCLVIII -3859 MMMDCCCLIX -3860 MMMDCCCLX -3861 MMMDCCCLXI -3862 MMMDCCCLXII -3863 MMMDCCCLXIII -3864 MMMDCCCLXIV -3865 MMMDCCCLXV -3866 MMMDCCCLXVI -3867 MMMDCCCLXVII -3868 MMMDCCCLXVIII -3869 MMMDCCCLXIX -3870 MMMDCCCLXX -3871 MMMDCCCLXXI -3872 MMMDCCCLXXII -3873 MMMDCCCLXXIII -3874 MMMDCCCLXXIV -3875 MMMDCCCLXXV -3876 MMMDCCCLXXVI -3877 MMMDCCCLXXVII -3878 MMMDCCCLXXVIII -3879 MMMDCCCLXXIX -3880 MMMDCCCLXXX -3881 MMMDCCCLXXXI -3882 MMMDCCCLXXXII -3883 MMMDCCCLXXXIII -3884 MMMDCCCLXXXIV -3885 MMMDCCCLXXXV -3886 MMMDCCCLXXXVI -3887 MMMDCCCLXXXVII -3888 MMMDCCCLXXXVIII -3889 MMMDCCCLXXXIX -3890 MMMDCCCXC -3891 MMMDCCCXCI -3892 MMMDCCCXCII -3893 MMMDCCCXCIII -3894 MMMDCCCXCIV -3895 MMMDCCCXCV -3896 MMMDCCCXCVI -3897 MMMDCCCXCVII -3898 MMMDCCCXCVIII -3899 MMMDCCCXCIX -3900 MMMCM -3901 MMMCMI -3902 MMMCMII -3903 MMMCMIII -3904 MMMCMIV -3905 MMMCMV -3906 MMMCMVI -3907 MMMCMVII -3908 MMMCMVIII -3909 MMMCMIX -3910 MMMCMX -3911 MMMCMXI -3912 MMMCMXII -3913 MMMCMXIII -3914 MMMCMXIV -3915 MMMCMXV -3916 MMMCMXVI -3917 MMMCMXVII -3918 MMMCMXVIII -3919 MMMCMXIX -3920 MMMCMXX -3921 MMMCMXXI -3922 MMMCMXXII -3923 MMMCMXXIII -3924 MMMCMXXIV -3925 MMMCMXXV -3926 MMMCMXXVI -3927 MMMCMXXVII -3928 MMMCMXXVIII -3929 MMMCMXXIX -3930 MMMCMXXX -3931 MMMCMXXXI -3932 MMMCMXXXII -3933 MMMCMXXXIII -3934 MMMCMXXXIV -3935 MMMCMXXXV -3936 MMMCMXXXVI -3937 MMMCMXXXVII -3938 MMMCMXXXVIII -3939 MMMCMXXXIX -3940 MMMCMXL -3941 MMMCMXLI -3942 MMMCMXLII -3943 MMMCMXLIII -3944 MMMCMXLIV -3945 MMMCMXLV -3946 MMMCMXLVI -3947 MMMCMXLVII -3948 MMMCMXLVIII -3949 MMMCMXLIX -3950 MMMCML -3951 MMMCMLI -3952 MMMCMLII -3953 MMMCMLIII -3954 MMMCMLIV -3955 MMMCMLV -3956 MMMCMLVI -3957 MMMCMLVII -3958 MMMCMLVIII -3959 MMMCMLIX -3960 MMMCMLX -3961 MMMCMLXI -3962 MMMCMLXII -3963 MMMCMLXIII -3964 MMMCMLXIV -3965 MMMCMLXV -3966 MMMCMLXVI -3967 MMMCMLXVII -3968 MMMCMLXVIII -3969 MMMCMLXIX -3970 MMMCMLXX -3971 MMMCMLXXI -3972 MMMCMLXXII -3973 MMMCMLXXIII -3974 MMMCMLXXIV -3975 MMMCMLXXV -3976 MMMCMLXXVI -3977 MMMCMLXXVII -3978 MMMCMLXXVIII -3979 MMMCMLXXIX -3980 MMMCMLXXX -3981 MMMCMLXXXI -3982 MMMCMLXXXII -3983 MMMCMLXXXIII -3984 MMMCMLXXXIV -3985 MMMCMLXXXV -3986 MMMCMLXXXVI -3987 MMMCMLXXXVII -3988 MMMCMLXXXVIII -3989 MMMCMLXXXIX -3990 MMMCMXC -3991 MMMCMXCI -3992 MMMCMXCII -3993 MMMCMXCIII -3994 MMMCMXCIV -3995 MMMCMXCV -3996 MMMCMXCVI -3997 MMMCMXCVII -3998 MMMCMXCVIII -3999 MMMCMXCIX \ No newline at end of file From 8d7b4e399a3e31cac1c9b4cbcfc80cfa39d4911b Mon Sep 17 00:00:00 2001 From: Distractic Date: Mon, 7 Aug 2023 08:17:11 +0200 Subject: [PATCH 131/143] tests(number-roman): Remove unnecessary test --- .../rushyverse/api/extension/NumberExtTest.kt | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/NumberExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/NumberExtTest.kt index d16991c3..617d4d08 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/NumberExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/NumberExtTest.kt @@ -62,28 +62,6 @@ class NumberExtTest { ex.message shouldBe "Number must be less than 4000" } - @ParameterizedTest - @ValueSource(ints = [1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000]) - fun `should return pure roman numerals`(number: Int) { - val expected = when (number) { - 1 -> "I" - 4 -> "IV" - 5 -> "V" - 9 -> "IX" - 10 -> "X" - 40 -> "XL" - 50 -> "L" - 90 -> "XC" - 100 -> "C" - 400 -> "CD" - 500 -> "D" - 900 -> "CM" - 1000 -> "M" - else -> throw IllegalArgumentException("Invalid number") - } - number.toRomanNumerals() shouldBe expected - } - @ParameterizedTest @CsvFileSource(resources = ["/cases/roman/numerals.csv"]) fun `should return complex roman numerals`(number: Int, expectedRoman: String) { From 18df32815e7d043fab4d0c313915376f67de7f72 Mon Sep 17 00:00:00 2001 From: Distractic Date: Mon, 7 Aug 2023 08:31:05 +0200 Subject: [PATCH 132/143] fix: Remove unnecessary inline --- .../com/github/rushyverse/api/extension/_CommandSender.kt | 6 ++---- .../kotlin/com/github/rushyverse/api/extension/_Player.kt | 3 +-- .../com/github/rushyverse/api/extension/_PlayerProfile.kt | 6 ++---- .../kotlin/com/github/rushyverse/api/item/CraftBuilder.kt | 3 +-- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_CommandSender.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_CommandSender.kt index 74d5a0cc..70548198 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_CommandSender.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_CommandSender.kt @@ -22,8 +22,7 @@ public fun CommandSender.sendMessageError(message: String){ * @param permissions Bukkit Permissions. * @return `true` if the sender has all permission, `false` otherwise. */ -@Suppress("NOTHING_TO_INLINE") -public inline fun CommandSender.hasPermissions(permissions: Array): Boolean = +public fun CommandSender.hasPermissions(permissions: Array): Boolean = permissions.all(this::hasPermission) /** @@ -33,6 +32,5 @@ public inline fun CommandSender.hasPermissions(permissions: Array): Bool * @param permissions Bukkit Permissions. * @return `true` if the sender has all permission, `false` otherwise. */ -@Suppress("NOTHING_TO_INLINE") -public inline fun CommandSender.hasPermissions(permissions: Iterable): Boolean = +public fun CommandSender.hasPermissions(permissions: Iterable): Boolean = permissions.all(this::hasPermission) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Player.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Player.kt index 95fb56ef..a1404525 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_Player.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Player.kt @@ -21,8 +21,7 @@ public inline fun Player.editProfile(editor: PlayerProfile.() -> Unit) { * @param item Item to compare the item in hands. * @return `true` if present in one of hands, `false` otherwise. */ -@Suppress("NOTHING_TO_INLINE") -public inline fun Player.itemInHand(item: ItemStack): Boolean = itemInHand { +public fun Player.itemInHand(item: ItemStack): Boolean = itemInHand { it == item } diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_PlayerProfile.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_PlayerProfile.kt index eb49477f..59c75a5f 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_PlayerProfile.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_PlayerProfile.kt @@ -14,8 +14,7 @@ public const val PROPERTY_TEXTURES: String = "textures" * @param skin Skin in string format. * @param signature Signature of the skin for validation. */ -@Suppress("NOTHING_TO_INLINE") -public inline fun PlayerProfile.setTextures(skin: String, signature: String? = null) { +public fun PlayerProfile.setTextures(skin: String, signature: String? = null) { setProperty(ProfileProperty(PROPERTY_TEXTURES, skin, signature)) } @@ -24,6 +23,5 @@ public inline fun PlayerProfile.setTextures(skin: String, signature: String? = n * @receiver Profile of a player. * @return The property with the name, `null` otherwise. */ -@Suppress("NOTHING_TO_INLINE") -public inline fun PlayerProfile.getTexturesProperty(): ProfileProperty? = +public fun PlayerProfile.getTexturesProperty(): ProfileProperty? = properties.find { it.name == PROPERTY_TEXTURES } diff --git a/src/main/kotlin/com/github/rushyverse/api/item/CraftBuilder.kt b/src/main/kotlin/com/github/rushyverse/api/item/CraftBuilder.kt index ca7e3ac2..cb52af5f 100644 --- a/src/main/kotlin/com/github/rushyverse/api/item/CraftBuilder.kt +++ b/src/main/kotlin/com/github/rushyverse/api/item/CraftBuilder.kt @@ -154,8 +154,7 @@ public class CraftBuilder { * Define the value of the [result] property. * @param item Item assign to the result of the craft. */ - @Suppress("NOTHING_TO_INLINE") - public inline fun result(item: ItemStack) { + public fun result(item: ItemStack) { result = item } From 5d0a83b0f7a597c41fb545fccd840563ec427992 Mon Sep 17 00:00:00 2001 From: Quentin Date: Mon, 7 Aug 2023 18:10:58 +0200 Subject: [PATCH 133/143] chore: Rename the TranslationProvider as Translator to avoid redundancy in the code --- .../com/github/rushyverse/api/Plugin.kt | 10 ++-- .../rushyverse/api/game/team/TeamType.kt | 4 +- ...rovider.kt => ResourceBundleTranslator.kt} | 12 ++--- .../{TranslationProvider.kt => Translator.kt} | 7 +-- ...est.kt => ResourceBundleTranslatorTest.kt} | 52 ++++--------------- 5 files changed, 21 insertions(+), 64 deletions(-) rename src/main/kotlin/com/github/rushyverse/api/translation/{ResourceBundleTranslationProvider.kt => ResourceBundleTranslator.kt} (85%) rename src/main/kotlin/com/github/rushyverse/api/translation/{TranslationProvider.kt => Translator.kt} (67%) rename src/test/kotlin/com/github/rushyverse/api/translation/{ResourceBundleTranslationProviderTest.kt => ResourceBundleTranslatorTest.kt} (71%) diff --git a/src/main/kotlin/com/github/rushyverse/api/Plugin.kt b/src/main/kotlin/com/github/rushyverse/api/Plugin.kt index e9c44cfa..c4dae683 100644 --- a/src/main/kotlin/com/github/rushyverse/api/Plugin.kt +++ b/src/main/kotlin/com/github/rushyverse/api/Plugin.kt @@ -14,7 +14,7 @@ import com.github.rushyverse.api.player.Client import com.github.rushyverse.api.player.ClientManager import com.github.rushyverse.api.player.ClientManagerImpl import com.github.rushyverse.api.serializer.* -import com.github.rushyverse.api.translation.ResourceBundleTranslationProvider +import com.github.rushyverse.api.translation.ResourceBundleTranslator import com.github.rushyverse.api.translation.registerResourceBundleForSupportedLocales import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin import kotlinx.serialization.modules.SerializersModule @@ -124,13 +124,13 @@ public abstract class Plugin : SuspendingJavaPlugin() { public abstract fun createClient(player: Player): Client /** - * Creates a new translation provider to fetch translations for the supported languages. + * Creates a new translator to fetch translations for the supported languages. * Can be overridden by derived classes to provide custom translation providers. * - * @return A translation provider configured for the supported languages. + * @return A translator configured for the supported languages. */ - protected open suspend fun createTranslationProvider(): ResourceBundleTranslationProvider = - ResourceBundleTranslationProvider().apply { + protected open suspend fun createTranslator(): ResourceBundleTranslator = + ResourceBundleTranslator().apply { registerResourceBundleForSupportedLocales(BUNDLE_API, ResourceBundle::getBundle) } } diff --git a/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt b/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt index b25a4a62..4816825e 100644 --- a/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt +++ b/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt @@ -2,7 +2,7 @@ package com.github.rushyverse.api.game.team import com.github.rushyverse.api.APIPlugin.Companion.BUNDLE_API import com.github.rushyverse.api.translation.SupportedLanguage -import com.github.rushyverse.api.translation.TranslationProvider +import com.github.rushyverse.api.translation.Translator import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.TextColor import java.util.* @@ -34,7 +34,7 @@ public enum class TeamType( * @return The translated name of the team. */ public fun name( - translator: TranslationProvider, + translator: Translator, locale: Locale = SupportedLanguage.ENGLISH.locale ): String = translator.translate("team.${name.lowercase()}", locale, BUNDLE_API) } diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProvider.kt b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslator.kt similarity index 85% rename from src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProvider.kt rename to src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslator.kt index e0774d67..a002b3cd 100644 --- a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProvider.kt +++ b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslator.kt @@ -6,9 +6,9 @@ import java.util.* /** * Loads the [ResourceBundle] called [bundleName] for all supported locales from [SupportedLanguage]. - * @see ResourceBundleTranslationProvider.registerResourceBundle + * @see ResourceBundleTranslator.registerResourceBundle */ -public fun ResourceBundleTranslationProvider.registerResourceBundleForSupportedLocales( +public fun ResourceBundleTranslator.registerResourceBundleForSupportedLocales( bundleName: String, loader: (String, Locale) -> ResourceBundle ) { @@ -23,14 +23,10 @@ private val logger = KotlinLogging.logger { } * Translation provider backed by Java's [ResourceBundle]s. This makes use of `.properties` files that are standard * across the Java ecosystem. */ -public open class ResourceBundleTranslationProvider : TranslationProvider { +public open class ResourceBundleTranslator : Translator { private val bundles: MutableMap, ResourceBundle> = mutableMapOf() - override fun get(key: String, locale: Locale, bundleName: String): String { - return getBundle(locale, bundleName).getString(key) - } - override fun translate( key: String, locale: Locale, @@ -38,7 +34,7 @@ public open class ResourceBundleTranslationProvider : TranslationProvider { arguments: Array ): String { val string = try { - get(key, locale, bundleName) + getBundle(locale, bundleName).getString(key) } catch (e: MissingResourceException) { logger.error("Unable to find translation for key '$key' in bundles: '$bundleName'", e) return key diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/TranslationProvider.kt b/src/main/kotlin/com/github/rushyverse/api/translation/Translator.kt similarity index 67% rename from src/main/kotlin/com/github/rushyverse/api/translation/TranslationProvider.kt rename to src/main/kotlin/com/github/rushyverse/api/translation/Translator.kt index f376a775..e93c0711 100644 --- a/src/main/kotlin/com/github/rushyverse/api/translation/TranslationProvider.kt +++ b/src/main/kotlin/com/github/rushyverse/api/translation/Translator.kt @@ -5,12 +5,7 @@ import java.util.* /** * Translation provider interface, in charge of taking string keys and returning translated strings. */ -public interface TranslationProvider { - - /** - * Get a translation by key from the given locale and bundle name. - */ - public fun get(key: String, locale: Locale, bundleName: String): String +public interface Translator { /** * Get a formatted translation using the provided arguments. diff --git a/src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProviderTest.kt b/src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslatorTest.kt similarity index 71% rename from src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProviderTest.kt rename to src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslatorTest.kt index 3f1bb9e5..62194ff8 100644 --- a/src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslationProviderTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslatorTest.kt @@ -12,13 +12,13 @@ private const val BUNDLE_NAME = "test_bundle" private const val SECOND_BUNDLE_NAME = "test_bundle_2" -class ResourceBundleTranslationProviderTest { +class ResourceBundleTranslatorTest { - private lateinit var provider: ResourceBundleTranslationProvider + private lateinit var provider: ResourceBundleTranslator @BeforeTest fun onBefore() { - provider = ResourceBundleTranslationProvider() + provider = ResourceBundleTranslator() } @Nested @@ -28,8 +28,8 @@ class ResourceBundleTranslationProviderTest { fun `should load a resource bundle`() { val locale = SupportedLanguage.ENGLISH.locale provider.registerResourceBundle(BUNDLE_NAME, locale, ResourceBundle::getBundle) - provider.get("test1", locale, BUNDLE_NAME) shouldBe "english_value_1" - provider.get("test2", locale, BUNDLE_NAME) shouldBe "english_value_2" + provider.translate("test1", locale, BUNDLE_NAME) shouldBe "english_value_1" + provider.translate("test2", locale, BUNDLE_NAME) shouldBe "english_value_2" } @Test @@ -37,8 +37,8 @@ class ResourceBundleTranslationProviderTest { provider.registerResourceBundleForSupportedLocales(BUNDLE_NAME, ResourceBundle::getBundle) SupportedLanguage.entries.forEach { val displayName = it.displayName.lowercase() - provider.get("test1", it.locale, BUNDLE_NAME) shouldBe "${displayName}_value_1" - provider.get("test2", it.locale, BUNDLE_NAME) shouldBe "${displayName}_value_2" + provider.translate("test1", it.locale, BUNDLE_NAME) shouldBe "${displayName}_value_1" + provider.translate("test2", it.locale, BUNDLE_NAME) shouldBe "${displayName}_value_2" } } @@ -47,42 +47,8 @@ class ResourceBundleTranslationProviderTest { val locale = SupportedLanguage.ENGLISH.locale provider.registerResourceBundle(BUNDLE_NAME, locale, ResourceBundle::getBundle) provider.registerResourceBundle(SECOND_BUNDLE_NAME, locale, ResourceBundle::getBundle) - provider.get("test1", locale, BUNDLE_NAME) shouldBe "english_value_1" - provider.get("simple_value", locale, SECOND_BUNDLE_NAME) shouldBe "English value" - } - } - - @Nested - inner class GetValue { - - @Test - fun `should throw an exception if the bundle is not registered`() { - val locale = SupportedLanguage.ENGLISH.locale - val ex = assertThrows { - provider.get("test1", locale, BUNDLE_NAME) - } - ex.bundleName shouldBe BUNDLE_NAME - ex.locale shouldBe locale - } - - @Test - fun `should throw an exception if the key is not found`() { - provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - assertThrows { - provider.get(randomString(), SupportedLanguage.ENGLISH.locale, BUNDLE_NAME) - } - } - - @Test - fun `should return the value for the given key`() { - provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - provider.get("test1", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME) shouldBe "english_value_1" - } - - @Test - fun `should return the default value if the value is not defined for language`() { - provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - provider.get("test_undefined", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME) shouldBe "default_value" + provider.translate("test1", locale, BUNDLE_NAME) shouldBe "english_value_1" + provider.translate("simple_value", locale, SECOND_BUNDLE_NAME) shouldBe "English value" } } From 5896f0242ce622936cba4b7a59edcd75c6a1b3ec Mon Sep 17 00:00:00 2001 From: Cizetux Date: Mon, 7 Aug 2023 20:16:51 +0200 Subject: [PATCH 134/143] fix: Chinese country code --- .../com/github/rushyverse/api/translation/SupportedLanguage.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt b/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt index 477532f2..4f7d5ee4 100644 --- a/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt +++ b/src/main/kotlin/com/github/rushyverse/api/translation/SupportedLanguage.kt @@ -44,5 +44,5 @@ public enum class SupportedLanguage( /** * Chinese language for China. */ - CHINESE("Chinese", Locale("zh", "ch")) + CHINESE("Chinese", Locale("zh", "cn")) } From eed5c986f24b3b79dca0bf694f73826b8b39876f Mon Sep 17 00:00:00 2001 From: Distractic <46402441+Distractic@users.noreply.github.com> Date: Tue, 8 Aug 2023 19:59:46 +0200 Subject: [PATCH 135/143] fix: Rework of translation system (#72) --- build.gradle.kts | 12 +- .../com/github/rushyverse/api/APIPlugin.kt | 2 + .../com/github/rushyverse/api/Plugin.kt | 74 ++++++++++- .../api/extension/_CommandSender.kt | 2 +- .../rushyverse/api/extension/_String.kt | 5 +- .../rushyverse/api/game/team/TeamType.kt | 2 +- .../rushyverse/api/listener/PlayerListener.kt | 5 + .../github/rushyverse/api/player/Client.kt | 13 +- .../api/player/language/LanguageManager.kt | 58 +++++++++ .../api/serializer/ItemStackSerializer.kt | 12 +- .../api/serializer/NamespacedSerializer.kt | 12 +- .../translation/ResourceBundleTranslator.kt | 10 +- .../rushyverse/api/translation/Translator.kt | 59 ++++++++- .../reader/YamlFileReaderTest.kt | 9 ++ .../rushyverse/api/extension/StringExtTest.kt | 118 ++++++++++++----- .../api/listener/PlayerListenerTest.kt | 9 +- .../player/language/LanguageManagerTest.kt | 104 +++++++++++++++ .../serializer/EnchantmentSerializerTest.kt | 1 + .../ResourceBundleTranslatorTest.kt | 119 ++++++++++++++---- .../github/rushyverse/api/utils/Generator.kt | 2 +- 20 files changed, 535 insertions(+), 93 deletions(-) create mode 100644 src/main/kotlin/com/github/rushyverse/api/player/language/LanguageManager.kt create mode 100644 src/test/kotlin/com/github/rushyverse/api/player/language/LanguageManagerTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index d9de892c..2d566328 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -69,7 +69,10 @@ dependencies { implementation("com.github.shynixn.mccoroutine:mccoroutine-bukkit-core:$mccoroutineVersion") // Minecraft server framework - compileOnly("io.papermc.paper:paper-api:$paperVersion") + "io.papermc.paper:paper-api:$paperVersion".let { + compileOnly(it) + testImplementation(it) + } // Scoreboard framework implementation("fr.mrmicky:fastboard:$fastboardVersion") @@ -77,21 +80,18 @@ dependencies { api("com.github.Rushyverse:core:6ae31a9250") // Tests + testImplementation("com.github.seeseemelk:MockBukkit-v1.20:$mockBukkitVersion") testImplementation(kotlin("test-junit5")) testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutineVersion") testImplementation("io.kotest:kotest-assertions-core:$kotestVersion") - implementation("io.kotest:kotest-assertions-json:$kotestVersion") + testImplementation("io.kotest:kotest-assertions-json:$kotestVersion") - testImplementation("io.papermc.paper:paper-api:$paperVersion") - implementation("com.github.seeseemelk:MockBukkit-v1.20:$mockBukkitVersion") testImplementation("org.junit.jupiter:junit-jupiter-params:$junitVersion") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitVersion") testImplementation("io.insert-koin:koin-test:$koinVersion") { exclude("org.jetbrains.kotlin", "kotlin-test-junit") } testImplementation("io.mockk:mockk:$mockkVersion") - testImplementation("org.slf4j:slf4j-api:$slf4jVersion") - testImplementation("org.slf4j:slf4j-simple:$slf4jVersion") } val javaVersion get() = JavaVersion.VERSION_17 diff --git a/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt b/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt index 52bd1473..1470f248 100644 --- a/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt +++ b/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt @@ -3,6 +3,7 @@ package com.github.rushyverse.api import com.github.rushyverse.api.game.SharedGameData import com.github.rushyverse.api.koin.CraftContext import com.github.rushyverse.api.koin.loadModule +import com.github.rushyverse.api.player.language.LanguageManager import com.github.rushyverse.api.player.scoreboard.ScoreboardManager import org.bukkit.Bukkit import org.bukkit.plugin.java.JavaPlugin @@ -31,6 +32,7 @@ public class APIPlugin : JavaPlugin() { loadModule(ID_API) { single { Bukkit.getServer() } single { ScoreboardManager() } + single { LanguageManager() } single { SharedGameData() } } } diff --git a/src/main/kotlin/com/github/rushyverse/api/Plugin.kt b/src/main/kotlin/com/github/rushyverse/api/Plugin.kt index c4dae683..31d770e5 100644 --- a/src/main/kotlin/com/github/rushyverse/api/Plugin.kt +++ b/src/main/kotlin/com/github/rushyverse/api/Plugin.kt @@ -5,22 +5,28 @@ import com.charleskorn.kaml.YamlConfiguration import com.github.rushyverse.api.APIPlugin.Companion.BUNDLE_API import com.github.rushyverse.api.configuration.reader.IFileReader import com.github.rushyverse.api.configuration.reader.YamlFileReader +import com.github.rushyverse.api.extension.asComponent import com.github.rushyverse.api.extension.registerListener import com.github.rushyverse.api.koin.CraftContext +import com.github.rushyverse.api.koin.inject import com.github.rushyverse.api.koin.loadModule import com.github.rushyverse.api.listener.PlayerListener import com.github.rushyverse.api.listener.VillagerListener import com.github.rushyverse.api.player.Client import com.github.rushyverse.api.player.ClientManager import com.github.rushyverse.api.player.ClientManagerImpl +import com.github.rushyverse.api.player.language.LanguageManager import com.github.rushyverse.api.serializer.* import com.github.rushyverse.api.translation.ResourceBundleTranslator +import com.github.rushyverse.api.translation.Translator import com.github.rushyverse.api.translation.registerResourceBundleForSupportedLocales import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.SerializersModuleBuilder import kotlinx.serialization.modules.contextual +import net.kyori.adventure.text.Component import org.bukkit.entity.Player +import org.jetbrains.annotations.Blocking import org.koin.core.module.Module import org.koin.dsl.bind import java.util.* @@ -30,14 +36,29 @@ import java.util.* * This abstract class provides necessary tools and life-cycle methods to facilitate the creation * and management of a plugin that utilizes asynchronous operations, dependency injection, and * other utility functions. + * @property id A unique identifier for this plugin. + * @property bundle The name of the resource bundle to use for this plugin. + * This ID is used for tasks like identifying the Koin application and loading Koin modules. */ -public abstract class Plugin : SuspendingJavaPlugin() { +public abstract class Plugin( + public val id: String, + public val bundle: String +) : SuspendingJavaPlugin() { /** - * A unique identifier for this plugin. This ID is used for tasks like identifying - * the Koin application, loading Koin modules, etc. + * Client manager linked to this plugin. */ - public abstract val id: String + public val clientManager: ClientManager by inject(id) + + /** + * Translator linked to this plugin. + */ + public val translator: Translator by inject(id) + + /** + * Common language manager for all plugins. + */ + public val languageManager: LanguageManager by inject() override suspend fun onEnableAsync() { super.onEnableAsync() @@ -45,6 +66,7 @@ public abstract class Plugin : SuspendingJavaPlugin() { CraftContext.startKoin(id) moduleBukkit() moduleClients() + moduleTranslation() registerListener { PlayerListener(this) } registerListener { VillagerListener(this) } @@ -69,6 +91,15 @@ public abstract class Plugin : SuspendingJavaPlugin() { single { ClientManagerImpl() } bind ClientManager::class } + /** + * Creates and loads a Koin module containing translation components. + * + * @return The Koin module for translation. + */ + protected fun moduleTranslation(): Module = loadModule(id) { + single { createTranslator() } bind Translator::class + } + /** * Creates and loads a Koin module with Bukkit-specific components. * Can be overridden by derived classes to provide additional or customized components. @@ -129,8 +160,39 @@ public abstract class Plugin : SuspendingJavaPlugin() { * * @return A translator configured for the supported languages. */ - protected open suspend fun createTranslator(): ResourceBundleTranslator = - ResourceBundleTranslator().apply { + @Blocking + protected open fun createTranslator(): ResourceBundleTranslator = + ResourceBundleTranslator(bundle).apply { registerResourceBundleForSupportedLocales(BUNDLE_API, ResourceBundle::getBundle) } + + /** + * Broadcasts a localized message to all players. + * + * This function groups players by their language preferences, translates the message once per language, + * and then sends the appropriate localized message to each player. + * + * @param players The players to whom the message should be sent. + * @param key The key used to look up the translation in the resource bundle. + * @param bundle The resource bundle to use for the translation. + * @param argumentBuilder A function that builds the arguments for the translation. + * @param messageModifier A function that modifies the translated message before it is sent. + * The modification must be chained. + */ + public suspend inline fun broadcast( + players: Collection, + key: String, + bundle: String = this.bundle, + messageModifier: (Component) -> Component = { it }, + argumentBuilder: Translator.(Locale) -> Array = { emptyArray() }, + ) { + players.groupBy { languageManager.get(it).locale } + .forEach { (lang, receiver) -> + val translatedComponent = translator + .get(key, lang, translator.argumentBuilder(lang), bundle) + .asComponent().let(messageModifier) + + receiver.forEach { it.sendMessage(translatedComponent) } + } + } } diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_CommandSender.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_CommandSender.kt index 70548198..ee24e26d 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_CommandSender.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_CommandSender.kt @@ -8,7 +8,7 @@ import org.bukkit.command.CommandSender * @receiver Sender that will receive the message. * @param message Message. */ -public fun CommandSender.sendMessageError(message: String){ +public fun CommandSender.sendMessageError(message: String) { sendMessage(text { content(message) color(NamedTextColor.RED) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt index 2c619408..e723f542 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt @@ -15,7 +15,7 @@ import java.util.* /** * MiniMessage instance to deserialize components without strict mode. */ -private val MINI_MESSAGE_NON_STRICT: MiniMessage = MiniMessage.builder() +public val MINI_MESSAGE_NON_STRICT: MiniMessage = MiniMessage.builder() .strict(false) .tags(StandardTags.defaults()) .build() @@ -231,9 +231,10 @@ public fun String.toFormattedLoreSequence(lineLength: Int = DEFAULT_LORE_LINE_LE * The [tagResolver] will be used to resolve the custom tags and replace values. * @receiver The string used to create the component. * @param tagResolver The tag resolver used to resolve the custom tags. + * @param miniMessage The mini message instance used to parse the string. * @return The component created from the string. */ public fun String.asComponent( vararg tagResolver: TagResolver, - miniMessage: MiniMessage = MINI_MESSAGE_NON_STRICT + miniMessage: MiniMessage = MINI_MESSAGE_NON_STRICT, ): Component = miniMessage.deserialize(this, *tagResolver) diff --git a/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt b/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt index 4816825e..cf325cc9 100644 --- a/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt +++ b/src/main/kotlin/com/github/rushyverse/api/game/team/TeamType.kt @@ -36,5 +36,5 @@ public enum class TeamType( public fun name( translator: Translator, locale: Locale = SupportedLanguage.ENGLISH.locale - ): String = translator.translate("team.${name.lowercase()}", locale, BUNDLE_API) + ): String = translator.get("team.${name.lowercase()}", locale, bundleName = BUNDLE_API) } diff --git a/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt index 39f77569..5cd49a03 100644 --- a/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt +++ b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt @@ -6,6 +6,7 @@ import com.github.rushyverse.api.koin.inject import com.github.rushyverse.api.player.Client import com.github.rushyverse.api.player.ClientManager import com.github.rushyverse.api.player.exception.ClientAlreadyExistsException +import com.github.rushyverse.api.player.language.LanguageManager import com.github.rushyverse.api.player.scoreboard.ScoreboardManager import kotlinx.coroutines.cancel import org.bukkit.entity.Player @@ -25,8 +26,11 @@ public class PlayerListener( ) : Listener { private val clients: ClientManager by inject(plugin.id) + private val scoreboardManager: ScoreboardManager by inject() + private val languageManager: LanguageManager by inject() + /** * Handle the join event to create and store a new client. * The client will be linked to the player. @@ -61,6 +65,7 @@ public class PlayerListener( public suspend fun onQuit(event: PlayerQuitEvent) { val player = event.player scoreboardManager.remove(player) + languageManager.remove(player) val client = clients.removeClient(player) ?: return client.cancel(SilentCancellationException("The player ${player.name} (${player.uniqueId}) left")) diff --git a/src/main/kotlin/com/github/rushyverse/api/player/Client.kt b/src/main/kotlin/com/github/rushyverse/api/player/Client.kt index 7e0b49c9..0127a2d3 100644 --- a/src/main/kotlin/com/github/rushyverse/api/player/Client.kt +++ b/src/main/kotlin/com/github/rushyverse/api/player/Client.kt @@ -4,6 +4,7 @@ import com.github.rushyverse.api.delegate.DelegatePlayer import com.github.rushyverse.api.extension.asComponent import com.github.rushyverse.api.koin.inject import com.github.rushyverse.api.player.exception.PlayerNotFoundException +import com.github.rushyverse.api.player.language.LanguageManager import com.github.rushyverse.api.player.scoreboard.ScoreboardManager import com.github.rushyverse.api.translation.SupportedLanguage import fr.mrmicky.fastboard.adventure.FastBoard @@ -21,14 +22,12 @@ import java.util.* public open class Client( public val playerUUID: UUID, coroutineScope: CoroutineScope, - /** - * The current language of the player. - */ - public var lang: SupportedLanguage = SupportedLanguage.ENGLISH ) : CoroutineScope by coroutineScope { private val scoreboardManager: ScoreboardManager by inject() + private val languageManager: LanguageManager by inject() + public val player: Player? by DelegatePlayer(playerUUID) /** @@ -69,4 +68,10 @@ public open class Client( */ public suspend fun scoreboard(): FastBoard = scoreboardManager.getOrCreate(requirePlayer()) + /** + * Get the language of the player. + * @return The language of the player. + */ + public suspend fun lang(): SupportedLanguage = languageManager.get(requirePlayer()) + } diff --git a/src/main/kotlin/com/github/rushyverse/api/player/language/LanguageManager.kt b/src/main/kotlin/com/github/rushyverse/api/player/language/LanguageManager.kt new file mode 100644 index 00000000..0eebda05 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/player/language/LanguageManager.kt @@ -0,0 +1,58 @@ +package com.github.rushyverse.api.player.language + +import com.github.rushyverse.api.translation.SupportedLanguage +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import org.bukkit.entity.Player + +/** + * Manages the languages for players within the game. + * This class ensures thread-safe operations on the languages by using mutex locks. + */ +public class LanguageManager { + + /** + * Mutex used to ensure thread-safe operations on the language map. + */ + private val mutex = Mutex() + + /** + * Private mutable map storing languages associated with player names. + */ + private val _languages = mutableMapOf() + + /** + * Public immutable view of the languages map. + */ + public val languages: Map = _languages + + /** + * Retrieves the languages for the specified player or creates a new one if it doesn't exist. + * This function is thread-safe and uses mutex locks to ensure atomic operations. + * + * @param player The player for whom the language is to be retrieved or created. + * @return The language associated with the player. + */ + public suspend fun get(player: Player): SupportedLanguage = mutex.withLock { + _languages.getOrDefault(player.name, SupportedLanguage.ENGLISH) + } + + /** + * Sets the language for the specified player. + * @param player The player for whom the language is to be set. + * @param lang The language to set. + */ + public suspend fun set(player: Player, lang: SupportedLanguage) { + mutex.withLock { _languages[player.name] = lang } + } + + /** + * Removes and deletes the language associated with the specified player. + * This function is thread-safe and uses mutex locks to ensure atomic operations. + * + * @param player The player whose language is to be removed. + */ + public suspend fun remove(player: Player) { + mutex.withLock { _languages.remove(player.name) } + } +} diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/ItemStackSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/ItemStackSerializer.kt index 1c282adc..fe314dd4 100644 --- a/src/main/kotlin/com/github/rushyverse/api/serializer/ItemStackSerializer.kt +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/ItemStackSerializer.kt @@ -82,29 +82,29 @@ public object ItemStackSerializer : KSerializer { encodeSerializableElement(descriptor, 1, amountSerializer, value.amount) encodeSerializableElement(descriptor, 2, enchantmentsSerializer, value.enchantments) - if(itemMeta == null) return@encodeStructure + if (itemMeta == null) return@encodeStructure encodeSerializableElement(descriptor, 3, unbreakableSerializer, itemMeta.isUnbreakable) encodeSerializableElement(descriptor, 4, customModelSerializer, itemMeta.let { - if(it.hasCustomModelData()) it.customModelData else null + if (it.hasCustomModelData()) it.customModelData else null }) encodeSerializableElement( descriptor, 5, destroyableKeysSerializer, - itemMeta.let { if(it.hasDestroyableKeys()) it.destroyableKeys.toList() else null } + itemMeta.let { if (it.hasDestroyableKeys()) it.destroyableKeys.toList() else null } ) encodeSerializableElement( descriptor, 6, placeableKeysSerializer, - itemMeta.let { if(it.hasPlaceableKeys()) it.placeableKeys.toList() else null } + itemMeta.let { if (it.hasPlaceableKeys()) it.placeableKeys.toList() else null } ) encodeSerializableElement(descriptor, 7, displayNameSerializer, itemMeta.let { - if(it.hasDisplayName()) it.displayName() else null + if (it.hasDisplayName()) it.displayName() else null }) encodeSerializableElement(descriptor, 8, loreSerializer, itemMeta.let { - if(it.hasLore()) it.lore() else null + if (it.hasLore()) it.lore() else null }) encodeSerializableElement( descriptor, diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/NamespacedSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/NamespacedSerializer.kt index 5da92163..49f2779a 100644 --- a/src/main/kotlin/com/github/rushyverse/api/serializer/NamespacedSerializer.kt +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/NamespacedSerializer.kt @@ -14,6 +14,14 @@ import org.bukkit.NamespacedKey */ public object NamespacedSerializer : KSerializer { + /** + * The default separator used to separate the namespace and the key. + */ + private const val DEFAULT_SEPARATOR = ":" + + /** + * Regex used to replace uppercase letters with "_[a-z]". + */ private val regexUppercase = Regex("([A-Z])") override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor( @@ -22,12 +30,12 @@ public object NamespacedSerializer : KSerializer { ) override fun serialize(encoder: Encoder, value: Namespaced) { - encoder.encodeString(value.namespace + NamespacedKey.DEFAULT_SEPARATOR + value.key) + encoder.encodeString(value.namespace + DEFAULT_SEPARATOR + value.key) } override fun deserialize(decoder: Decoder): Namespaced { return decoder.decodeString().let { decodedString -> - val namespacedString = decodedString.split(NamespacedKey.DEFAULT_SEPARATOR).map { + val namespacedString = decodedString.split(DEFAULT_SEPARATOR).map { // Replace " " to "_". Example: blue wool -> blue_wool it.replace(' ', '_') // Replace "A-Z" to "_a-z". Example: blueWool -> blue_wool diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslator.kt b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslator.kt index a002b3cd..81c48e89 100644 --- a/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslator.kt +++ b/src/main/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslator.kt @@ -23,15 +23,15 @@ private val logger = KotlinLogging.logger { } * Translation provider backed by Java's [ResourceBundle]s. This makes use of `.properties` files that are standard * across the Java ecosystem. */ -public open class ResourceBundleTranslator : Translator { +public open class ResourceBundleTranslator(defaultBundle: String) : Translator(defaultBundle) { private val bundles: MutableMap, ResourceBundle> = mutableMapOf() - override fun translate( + override fun get( key: String, locale: Locale, - bundleName: String, - arguments: Array + args: Array, + bundleName: String ): String { val string = try { getBundle(locale, bundleName).getString(key) @@ -40,7 +40,7 @@ public open class ResourceBundleTranslator : Translator { return key } - return MessageFormat(string, locale).format(arguments) + return MessageFormat(string, locale).format(args) } /** diff --git a/src/main/kotlin/com/github/rushyverse/api/translation/Translator.kt b/src/main/kotlin/com/github/rushyverse/api/translation/Translator.kt index e93c0711..1e109c59 100644 --- a/src/main/kotlin/com/github/rushyverse/api/translation/Translator.kt +++ b/src/main/kotlin/com/github/rushyverse/api/translation/Translator.kt @@ -1,19 +1,70 @@ package com.github.rushyverse.api.translation +import com.github.rushyverse.api.extension.asComponent +import net.kyori.adventure.text.Component import java.util.* +/** + * Get a translation using the provided arguments. + * @receiver Translator The translator to use. + * @param key Key in the bundle to find the translation for. + * @param locale Language to translate to. + * @param bundleName Name of the bundle to use, by default [Translator.defaultBundle]. + * @return The translated string or the key if no translation was found in a [Component]. + */ +public fun Translator.getComponent( + key: String, + locale: Locale, + bundleName: String = defaultBundle, +): Component = getComponent(key, locale, emptyArray(), bundleName) + +/** + * Get a translation using the provided arguments. + * @receiver Translator The translator to use. + * @param key Key in the bundle to find the translation for. + * @param locale Language to translate to. + * @param args Arguments to format the translation with. + * @param bundleName Name of the bundle to use, by default [Translator.defaultBundle]. + * @return The translated string or the key if no translation was found in a [Component]. + */ +public fun Translator.getComponent( + key: String, + locale: Locale, + args: Array = emptyArray(), + bundleName: String = defaultBundle +): Component = get(key, locale, args, bundleName).asComponent() + /** * Translation provider interface, in charge of taking string keys and returning translated strings. + * @property defaultBundle The default bundle to use for translations. */ -public interface Translator { +public abstract class Translator(public val defaultBundle: String) { + + /** + * Get a translation using the provided arguments. + * @param key Key in the bundle to find the translation for. + * @param locale Language to translate to. + * @param bundleName Name of the bundle to use, by default [defaultBundle]. + * @return The translated string or the key if no translation was found. + */ + public fun get( + key: String, + locale: Locale, + bundleName: String = defaultBundle + ): String = get(key, locale, emptyArray(), bundleName) /** * Get a formatted translation using the provided arguments. + * @param key Key in the bundle to find the translation for. + * @param locale Language to translate to. + * @param args Arguments to format the translation with. + * @param bundleName Name of the bundle to use, by default [defaultBundle]. + * @return The translated string or the key if no translation was found. */ - public fun translate( + public abstract fun get( key: String, locale: Locale, - bundleName: String, - arguments: Array = emptyArray() + args: Array = emptyArray(), + bundleName: String = defaultBundle ): String } diff --git a/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt b/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt index 7bc41fd6..7b12f3f8 100644 --- a/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/configuration/reader/YamlFileReaderTest.kt @@ -87,6 +87,15 @@ class YamlFileReaderTest { shouldCreateFileAndDecode(configFile, value) { reader.readConfigurationFile(TestValue.serializer(), it) } } + @ParameterizedTest + @CsvSource( + "fake_config.yml, withoutParent", + "configuration/fake_config.yml, withParent" + ) + fun `should create file and decode it using reified type`(configFile: String, value: String) { + shouldCreateFileAndDecode(configFile, value) { reader.readConfigurationFile(it) } + } + @Test fun `should not create file if exists and read it`() { shouldNotCreateFileIfExistsAndDecode { reader.readConfigurationFile(TestValue.serializer(), it) } diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt index ecf04cf7..78a73efb 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt @@ -2,6 +2,12 @@ package com.github.rushyverse.api.extension import com.github.rushyverse.api.utils.randomString import io.kotest.matchers.shouldBe +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.format.TextDecoration +import net.kyori.adventure.text.minimessage.MiniMessage +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.assertThrows @@ -50,12 +56,14 @@ class StringExtTest { } @ParameterizedTest - @ValueSource(strings = [ - "", - "a", - "c7e4ca3236d942408e53de44bef8eeeb", - "c7e4ca32-36d9-4240-8e53-de44bef8eeeba" - ]) + @ValueSource( + strings = [ + "", + "a", + "c7e4ca3236d942408e53de44bef8eeeb", + "c7e4ca32-36d9-4240-8e53-de44bef8eeeba" + ] + ) fun `throws exception if invalid`(value: String) { assertThrows { value.toUUIDStrict() @@ -70,12 +78,14 @@ class StringExtTest { } @ParameterizedTest - @ValueSource(strings = [ - "", - "a", - "c7e4ca3236d942408e53de44bef8eeeb", - "c7e4ca32-36d9-4240-8e53-de44bef8eeeba" - ]) + @ValueSource( + strings = [ + "", + "a", + "c7e4ca3236d942408e53de44bef8eeeb", + "c7e4ca32-36d9-4240-8e53-de44bef8eeeba" + ] + ) fun `nulls if invalid`(value: String) { assertNull(value.toUUIDStrictOrNull()) } @@ -86,22 +96,26 @@ class StringExtTest { inner class NoStrict { @ParameterizedTest - @ValueSource(strings = [ - "c7e4ca3236d942408e53de44bef8eeeb", - "c7e4ca32-36d9-4240-8e53-de44bef8eeeb" - ]) + @ValueSource( + strings = [ + "c7e4ca3236d942408e53de44bef8eeeb", + "c7e4ca32-36d9-4240-8e53-de44bef8eeeb" + ] + ) fun `can convert if the string is valid`(value: String) { val uuid = UUID.fromString("c7e4ca32-36d9-4240-8e53-de44bef8eeeb") assertEquals(uuid, value.toUUID()) } @ParameterizedTest - @ValueSource(strings = [ - "", - "a", - "c7e4ca3236d942408e53de44bef8eeeba", - "c7e4ca32-36d9-4240-8e53-de44bef8eeeba" - ]) + @ValueSource( + strings = [ + "", + "a", + "c7e4ca3236d942408e53de44bef8eeeba", + "c7e4ca32-36d9-4240-8e53-de44bef8eeeba" + ] + ) fun `throws exception if invalid`(value: String) { assertThrows { value.toUUID() @@ -116,12 +130,14 @@ class StringExtTest { } @ParameterizedTest - @ValueSource(strings = [ - "", - "a", - "c7e4ca3236d942408e53de44bef8eeeba", - "c7e4ca32-36d9-4240-8e53-de44bef8eeeba" - ]) + @ValueSource( + strings = [ + "", + "a", + "c7e4ca3236d942408e53de44bef8eeeba", + "c7e4ca32-36d9-4240-8e53-de44bef8eeeba" + ] + ) fun `nulls if invalid`(value: String) { assertNull(value.toUUIDStrictOrNull()) } @@ -159,4 +175,50 @@ class StringExtTest { } } + + @Nested + inner class AsComponent { + + @Test + fun `should transform non empty string`() { + val string = randomString() + string.asComponent() shouldBe Component.text(string) + } + + @Test + fun `should transform empty string`() { + "".asComponent() shouldBe Component.empty() + } + + @Test + fun `should read mini message tag`() { + val string = "hello" + string.asComponent() shouldBe Component.text("hello").color(NamedTextColor.RED) + .decorate(TextDecoration.BOLD) + } + + @Test + fun `should use tag if defined`() { + val string = "hello" + string.asComponent( + Placeholder.parsed("test", "myvalue") + ) shouldBe Component.text("myvaluehello").color(NamedTextColor.RED) + } + + @Test + fun `should use custom instance of mini message`() { + val string = "hello" + + val miniMessage = MiniMessage.builder() + .tags( + TagResolver.resolver( + Placeholder.parsed("test", "myvalue") + ) + ) + .build() + + string.asComponent(miniMessage = miniMessage) shouldBe Component.text("myvaluehello") + } + + } } diff --git a/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt b/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt index 6dd5fba1..ce64c363 100644 --- a/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt @@ -5,6 +5,7 @@ import com.github.rushyverse.api.player.Client import com.github.rushyverse.api.player.ClientManager import com.github.rushyverse.api.player.ClientManagerImpl import com.github.rushyverse.api.player.exception.ClientAlreadyExistsException +import com.github.rushyverse.api.player.language.LanguageManager import com.github.rushyverse.api.player.scoreboard.ScoreboardManager import com.github.rushyverse.api.utils.randomString import io.mockk.coJustRun @@ -34,6 +35,8 @@ class PlayerListenerTest : AbstractKoinTest() { private lateinit var scoreboardManagerMock: ScoreboardManager + private lateinit var languageManager: LanguageManager + @BeforeTest override fun onBefore() { super.onBefore() @@ -42,9 +45,11 @@ class PlayerListenerTest : AbstractKoinTest() { single { clientManager } } - scoreboardManagerMock = mockk() + scoreboardManagerMock = mockk() + languageManager = mockk() loadApiTestModule { single { scoreboardManagerMock } + single { languageManager } } listener = PlayerListener(plugin) } @@ -115,6 +120,7 @@ class PlayerListenerTest : AbstractKoinTest() { fun `client linked to the player is removed and cancelled`() = runTest { val player = createPlayerMock() coJustRun { scoreboardManagerMock.remove(any()) } + coJustRun { languageManager.remove(any()) } val client = Client(player.uniqueId, CoroutineScope(Dispatchers.Main + SupervisorJob())) clientManager.put(player, client) @@ -124,6 +130,7 @@ class PlayerListenerTest : AbstractKoinTest() { assertEquals(0, clientManager.clients.size) assertFalse { client.isActive } coVerify { scoreboardManagerMock.remove(player) } + coVerify { languageManager.remove(player) } } private fun createEvent(player: Player): PlayerQuitEvent { diff --git a/src/test/kotlin/com/github/rushyverse/api/player/language/LanguageManagerTest.kt b/src/test/kotlin/com/github/rushyverse/api/player/language/LanguageManagerTest.kt new file mode 100644 index 00000000..0b4a15bb --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/player/language/LanguageManagerTest.kt @@ -0,0 +1,104 @@ +package com.github.rushyverse.api.player.language + +import be.seeseemelk.mockbukkit.MockBukkit +import be.seeseemelk.mockbukkit.ServerMock +import com.github.rushyverse.api.translation.SupportedLanguage +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Nested +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test + +class LanguageManagerTest { + + private lateinit var manager: LanguageManager + + private lateinit var server: ServerMock + + @BeforeTest + fun onBefore() { + manager = LanguageManager() + server = MockBukkit.mock() + } + + @AfterTest + fun onAfter() { + MockBukkit.unmock() + } + + @Nested + inner class Get { + + @Test + fun `should return the language associated with the player`() = runTest { + val player = server.addPlayer() + manager.set(player, SupportedLanguage.FRENCH) + manager.get(player) shouldBe SupportedLanguage.FRENCH + } + + @Test + fun `should return the default language if the player has no language`() = runTest { + val player = server.addPlayer() + manager.get(player) shouldBe SupportedLanguage.ENGLISH + } + + } + + @Nested + inner class Set { + + @Test + fun `should overwrite the language for the player`() = runTest { + val player = server.addPlayer() + manager.set(player, SupportedLanguage.FRENCH) + manager.get(player) shouldBe SupportedLanguage.FRENCH + + manager.set(player, SupportedLanguage.ENGLISH) + manager.get(player) shouldBe SupportedLanguage.ENGLISH + } + + @Test + fun `should set for several players`() = runTest { + val player1 = server.addPlayer() + val player2 = server.addPlayer() + + manager.set(player1, SupportedLanguage.FRENCH) + manager.set(player2, SupportedLanguage.GERMAN) + + manager.get(player1) shouldBe SupportedLanguage.FRENCH + manager.get(player2) shouldBe SupportedLanguage.GERMAN + } + + } + + @Nested + inner class Remove { + + @Test + fun `should remove the language associated with the player`() = runTest { + val player = server.addPlayer() + val player2 = server.addPlayer() + + manager.set(player, SupportedLanguage.FRENCH) + manager.get(player) shouldBe SupportedLanguage.FRENCH + + manager.set(player2, SupportedLanguage.GERMAN) + manager.get(player2) shouldBe SupportedLanguage.GERMAN + + manager.remove(player) + + manager.get(player) shouldBe SupportedLanguage.ENGLISH + manager.get(player2) shouldBe SupportedLanguage.GERMAN + } + + @Test + fun `should do nothing if the player has no language`() = runTest { + val player = server.addPlayer() + manager.remove(player) + + manager.get(player) shouldBe SupportedLanguage.ENGLISH + } + + } +} diff --git a/src/test/kotlin/com/github/rushyverse/api/serializer/EnchantmentSerializerTest.kt b/src/test/kotlin/com/github/rushyverse/api/serializer/EnchantmentSerializerTest.kt index 85593543..4486bb38 100644 --- a/src/test/kotlin/com/github/rushyverse/api/serializer/EnchantmentSerializerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/serializer/EnchantmentSerializerTest.kt @@ -24,6 +24,7 @@ class EnchantmentSerializerTest { class EnchantmentMock(private val enchantmentName: String, namespace: NamespacedKey) : Enchantment(namespace) { override fun translationKey(): String = error("Not implemented") + @Deprecated("Deprecated in Java") override fun getName(): String = enchantmentName override fun getMaxLevel(): Int = error("Not implemented") diff --git a/src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslatorTest.kt b/src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslatorTest.kt index 62194ff8..364ec528 100644 --- a/src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslatorTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/translation/ResourceBundleTranslatorTest.kt @@ -2,6 +2,7 @@ package com.github.rushyverse.api.translation import com.github.rushyverse.api.utils.randomString import io.kotest.matchers.shouldBe +import net.kyori.adventure.text.Component import org.junit.jupiter.api.Nested import org.junit.jupiter.api.assertThrows import java.util.* @@ -18,7 +19,7 @@ class ResourceBundleTranslatorTest { @BeforeTest fun onBefore() { - provider = ResourceBundleTranslator() + provider = ResourceBundleTranslator(BUNDLE_NAME) } @Nested @@ -28,8 +29,8 @@ class ResourceBundleTranslatorTest { fun `should load a resource bundle`() { val locale = SupportedLanguage.ENGLISH.locale provider.registerResourceBundle(BUNDLE_NAME, locale, ResourceBundle::getBundle) - provider.translate("test1", locale, BUNDLE_NAME) shouldBe "english_value_1" - provider.translate("test2", locale, BUNDLE_NAME) shouldBe "english_value_2" + provider.get("test1", locale, bundleName = BUNDLE_NAME) shouldBe "english_value_1" + provider.get("test2", locale, bundleName = BUNDLE_NAME) shouldBe "english_value_2" } @Test @@ -37,8 +38,8 @@ class ResourceBundleTranslatorTest { provider.registerResourceBundleForSupportedLocales(BUNDLE_NAME, ResourceBundle::getBundle) SupportedLanguage.entries.forEach { val displayName = it.displayName.lowercase() - provider.translate("test1", it.locale, BUNDLE_NAME) shouldBe "${displayName}_value_1" - provider.translate("test2", it.locale, BUNDLE_NAME) shouldBe "${displayName}_value_2" + provider.get("test1", it.locale, bundleName = BUNDLE_NAME) shouldBe "${displayName}_value_1" + provider.get("test2", it.locale, bundleName = BUNDLE_NAME) shouldBe "${displayName}_value_2" } } @@ -47,8 +48,8 @@ class ResourceBundleTranslatorTest { val locale = SupportedLanguage.ENGLISH.locale provider.registerResourceBundle(BUNDLE_NAME, locale, ResourceBundle::getBundle) provider.registerResourceBundle(SECOND_BUNDLE_NAME, locale, ResourceBundle::getBundle) - provider.translate("test1", locale, BUNDLE_NAME) shouldBe "english_value_1" - provider.translate("simple_value", locale, SECOND_BUNDLE_NAME) shouldBe "English value" + provider.get("test1", locale, bundleName = BUNDLE_NAME) shouldBe "english_value_1" + provider.get("simple_value", locale, bundleName = SECOND_BUNDLE_NAME) shouldBe "English value" } } @@ -58,16 +59,16 @@ class ResourceBundleTranslatorTest { @Test fun `should return the value for the given key`() { provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - provider.translate("test1", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME) shouldBe "english_value_1" + provider.get("test1", SupportedLanguage.ENGLISH.locale, bundleName = BUNDLE_NAME) shouldBe "english_value_1" } @Test fun `should return the value for the given key with the given array arguments`() { provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - provider.translate( - "test_args", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME, arrayOf( + provider.get( + "test_args", SupportedLanguage.ENGLISH.locale, arrayOf( "with arguments" - ) + ), BUNDLE_NAME ) shouldBe "english_value with arguments" } @@ -75,7 +76,7 @@ class ResourceBundleTranslatorTest { fun `should return the key if the bundle is not registered`() { val locale = SupportedLanguage.ENGLISH.locale val ex = assertThrows { - provider.translate("test1", locale, BUNDLE_NAME) + provider.get("test1", locale, bundleName = BUNDLE_NAME) } ex.bundleName shouldBe BUNDLE_NAME ex.locale shouldBe locale @@ -85,36 +86,44 @@ class ResourceBundleTranslatorTest { fun `should return the key if the value is not defined for language`() { provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) val key = randomString() - provider.translate(key, SupportedLanguage.ENGLISH.locale, BUNDLE_NAME) shouldBe key + provider.get(key, SupportedLanguage.ENGLISH.locale, bundleName = BUNDLE_NAME) shouldBe key } @Test fun `should return the key if the value is not defined for language with the given array arguments`() { provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) val key = randomString() - provider.translate(key, SupportedLanguage.ENGLISH.locale, BUNDLE_NAME, arrayOf("test")) shouldBe key + provider.get(key, SupportedLanguage.ENGLISH.locale, arrayOf("test"), BUNDLE_NAME) shouldBe key } @Test fun `should return the default value if the value is not defined for language`() { provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - provider.translate("test_undefined", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME) shouldBe "default_value" + provider.get( + "test_undefined", + SupportedLanguage.ENGLISH.locale, + bundleName = BUNDLE_NAME + ) shouldBe "default_value" } @Test fun `should return the value with template for args if no replacement args are defined`() { provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - provider.translate("test_args", SupportedLanguage.ENGLISH.locale, BUNDLE_NAME) shouldBe "english_value {0}" + provider.get( + "test_args", + SupportedLanguage.ENGLISH.locale, + bundleName = BUNDLE_NAME + ) shouldBe "english_value {0}" } @Test fun `should return the value with plural syntax`() { provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - provider.translate( + provider.get( "test_plural", SupportedLanguage.ENGLISH.locale, - BUNDLE_NAME, - arrayOf(2) + arrayOf(2), + BUNDLE_NAME ) shouldBe "Need 2 players." } @@ -122,26 +131,84 @@ class ResourceBundleTranslatorTest { fun `should return the value with singular syntax`() { provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) - provider.translate( + provider.get( "test_plural", SupportedLanguage.ENGLISH.locale, - BUNDLE_NAME, - arrayOf(1) + arrayOf(1), + BUNDLE_NAME ) shouldBe "Need 1 player." - provider.translate( + provider.get( "test_plural", SupportedLanguage.ENGLISH.locale, - BUNDLE_NAME, - arrayOf(0) + arrayOf(0), + BUNDLE_NAME ) shouldBe "Need 0 player." } @Test fun `should return the UTF-8 value`() { provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.FRENCH.locale, ResourceBundle::getBundle) - provider.translate("test1", SupportedLanguage.FRENCH.locale, BUNDLE_NAME) shouldBe "français_value_1" + provider.get("test1", SupportedLanguage.FRENCH.locale, bundleName = BUNDLE_NAME) shouldBe "français_value_1" + } + + @Test + fun `should use default bundle if no bundle is specified`() { + provider = ResourceBundleTranslator(BUNDLE_NAME) + provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) + + provider.get("test1", SupportedLanguage.ENGLISH.locale) shouldBe "english_value_1" + // Key not found in default bundle + provider.get("simple_value", SupportedLanguage.ENGLISH.locale) shouldBe "simple_value" + + provider = ResourceBundleTranslator(SECOND_BUNDLE_NAME) + provider.registerResourceBundle( + SECOND_BUNDLE_NAME, + SupportedLanguage.ENGLISH.locale, + ResourceBundle::getBundle + ) + + provider.get("simple_value", SupportedLanguage.ENGLISH.locale) shouldBe "English value" + // Key not found in default bundle + provider.get("test1", SupportedLanguage.ENGLISH.locale) shouldBe "test1" + } + + } + + @Nested + inner class TranslateValueToComponent { + + @Test + fun `should return the value for the given key`() { + provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) + provider.getComponent( + "test1", + SupportedLanguage.ENGLISH.locale, + bundleName = BUNDLE_NAME + ) shouldBe Component.text("english_value_1") + } + + @Test + fun `should return the value for the given key with the given array arguments`() { + provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) + provider.getComponent( + "test_args", + SupportedLanguage.ENGLISH.locale, + arrayOf("with arguments"), + bundleName = BUNDLE_NAME + ) shouldBe Component.text("english_value with arguments") } + @Test + fun `should return key in component if not found`() { + provider.registerResourceBundle(BUNDLE_NAME, SupportedLanguage.ENGLISH.locale, ResourceBundle::getBundle) + + val key = randomString() + provider.getComponent( + key, + SupportedLanguage.ENGLISH.locale, + bundleName = BUNDLE_NAME + ) shouldBe Component.text(key) + } } } diff --git a/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt b/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt index 46099a9c..721a4201 100644 --- a/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt +++ b/src/test/kotlin/com/github/rushyverse/api/utils/Generator.kt @@ -22,7 +22,7 @@ fun randomFloat(from: Float = Float.MIN_VALUE, until: Float = Float.MAX_VALUE) = fun randomDouble(from: Double = Double.MIN_VALUE, until: Double = Double.MAX_VALUE) = Random.nextDouble(from, until) -inline fun > randomEnum(): T { +inline fun > randomEnum(): T { return enumValues().random() } From 866551bbf4349bedc3880b698378c42d993d1c0f Mon Sep 17 00:00:00 2001 From: Distractic Date: Fri, 11 Aug 2023 22:49:34 +0200 Subject: [PATCH 136/143] fix: Player quit handle in API for lang & scoreboard --- .../com/github/rushyverse/api/APIPlugin.kt | 6 +++ .../rushyverse/api/listener/PlayerListener.kt | 9 ----- .../api/listener/api/LanguageListener.kt | 25 ++++++++++++ .../api/listener/api/ScoreboardListener.kt | 25 ++++++++++++ .../api/listener/PlayerListenerTest.kt | 18 --------- .../api/listener/api/LanguageListenerTest.kt | 39 +++++++++++++++++++ .../listener/api/ScoreboardListenerTest.kt | 39 +++++++++++++++++++ 7 files changed, 134 insertions(+), 27 deletions(-) create mode 100644 src/main/kotlin/com/github/rushyverse/api/listener/api/LanguageListener.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/listener/api/ScoreboardListener.kt create mode 100644 src/test/kotlin/com/github/rushyverse/api/listener/api/LanguageListenerTest.kt create mode 100644 src/test/kotlin/com/github/rushyverse/api/listener/api/ScoreboardListenerTest.kt diff --git a/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt b/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt index 1470f248..2bb5d419 100644 --- a/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt +++ b/src/main/kotlin/com/github/rushyverse/api/APIPlugin.kt @@ -1,8 +1,11 @@ package com.github.rushyverse.api +import com.github.rushyverse.api.extension.registerListener import com.github.rushyverse.api.game.SharedGameData import com.github.rushyverse.api.koin.CraftContext import com.github.rushyverse.api.koin.loadModule +import com.github.rushyverse.api.listener.api.LanguageListener +import com.github.rushyverse.api.listener.api.ScoreboardListener import com.github.rushyverse.api.player.language.LanguageManager import com.github.rushyverse.api.player.scoreboard.ScoreboardManager import org.bukkit.Bukkit @@ -35,6 +38,9 @@ public class APIPlugin : JavaPlugin() { single { LanguageManager() } single { SharedGameData() } } + + registerListener { LanguageListener() } + registerListener { ScoreboardListener() } } override fun onDisable() { diff --git a/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt index 5cd49a03..8ec3ae3c 100644 --- a/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt +++ b/src/main/kotlin/com/github/rushyverse/api/listener/PlayerListener.kt @@ -6,8 +6,6 @@ import com.github.rushyverse.api.koin.inject import com.github.rushyverse.api.player.Client import com.github.rushyverse.api.player.ClientManager import com.github.rushyverse.api.player.exception.ClientAlreadyExistsException -import com.github.rushyverse.api.player.language.LanguageManager -import com.github.rushyverse.api.player.scoreboard.ScoreboardManager import kotlinx.coroutines.cancel import org.bukkit.entity.Player import org.bukkit.event.EventHandler @@ -27,10 +25,6 @@ public class PlayerListener( private val clients: ClientManager by inject(plugin.id) - private val scoreboardManager: ScoreboardManager by inject() - - private val languageManager: LanguageManager by inject() - /** * Handle the join event to create and store a new client. * The client will be linked to the player. @@ -64,9 +58,6 @@ public class PlayerListener( @EventHandler(priority = EventPriority.HIGHEST) public suspend fun onQuit(event: PlayerQuitEvent) { val player = event.player - scoreboardManager.remove(player) - languageManager.remove(player) - val client = clients.removeClient(player) ?: return client.cancel(SilentCancellationException("The player ${player.name} (${player.uniqueId}) left")) } diff --git a/src/main/kotlin/com/github/rushyverse/api/listener/api/LanguageListener.kt b/src/main/kotlin/com/github/rushyverse/api/listener/api/LanguageListener.kt new file mode 100644 index 00000000..3b8d3b00 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/listener/api/LanguageListener.kt @@ -0,0 +1,25 @@ +package com.github.rushyverse.api.listener.api + +import com.github.rushyverse.api.koin.inject +import com.github.rushyverse.api.player.language.LanguageManager +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.player.PlayerQuitEvent + +/** + * Listener to manage language data when a player enters or leaves the server. + */ +public class LanguageListener : Listener { + + private val languageManager: LanguageManager by inject() + + /** + * Listen [PlayerQuitEvent] to remove the player from the language manager. + * @param event Event. + */ + @EventHandler + public suspend fun onQuit(event: PlayerQuitEvent) { + val player = event.player + languageManager.remove(player) + } +} diff --git a/src/main/kotlin/com/github/rushyverse/api/listener/api/ScoreboardListener.kt b/src/main/kotlin/com/github/rushyverse/api/listener/api/ScoreboardListener.kt new file mode 100644 index 00000000..06794e97 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/listener/api/ScoreboardListener.kt @@ -0,0 +1,25 @@ +package com.github.rushyverse.api.listener.api + +import com.github.rushyverse.api.koin.inject +import com.github.rushyverse.api.player.scoreboard.ScoreboardManager +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.player.PlayerQuitEvent + +/** + * Listener to manage scoreboard data when a player enters or leaves the server. + */ +public class ScoreboardListener : Listener { + + private val scoreboardManager: ScoreboardManager by inject() + + /** + * Listen [PlayerQuitEvent] to remove the player from the scoreboard manager. + * @param event Event. + */ + @EventHandler + public suspend fun onQuit(event: PlayerQuitEvent) { + val player = event.player + scoreboardManager.remove(player) + } +} diff --git a/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt b/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt index ce64c363..b1fa0770 100644 --- a/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/listener/PlayerListenerTest.kt @@ -5,11 +5,7 @@ import com.github.rushyverse.api.player.Client import com.github.rushyverse.api.player.ClientManager import com.github.rushyverse.api.player.ClientManagerImpl import com.github.rushyverse.api.player.exception.ClientAlreadyExistsException -import com.github.rushyverse.api.player.language.LanguageManager -import com.github.rushyverse.api.player.scoreboard.ScoreboardManager import com.github.rushyverse.api.utils.randomString -import io.mockk.coJustRun -import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.CoroutineScope @@ -33,10 +29,6 @@ class PlayerListenerTest : AbstractKoinTest() { private lateinit var listener: PlayerListener - private lateinit var scoreboardManagerMock: ScoreboardManager - - private lateinit var languageManager: LanguageManager - @BeforeTest override fun onBefore() { super.onBefore() @@ -45,12 +37,6 @@ class PlayerListenerTest : AbstractKoinTest() { single { clientManager } } - scoreboardManagerMock = mockk() - languageManager = mockk() - loadApiTestModule { - single { scoreboardManagerMock } - single { languageManager } - } listener = PlayerListener(plugin) } @@ -119,8 +105,6 @@ class PlayerListenerTest : AbstractKoinTest() { @Test fun `client linked to the player is removed and cancelled`() = runTest { val player = createPlayerMock() - coJustRun { scoreboardManagerMock.remove(any()) } - coJustRun { languageManager.remove(any()) } val client = Client(player.uniqueId, CoroutineScope(Dispatchers.Main + SupervisorJob())) clientManager.put(player, client) @@ -129,8 +113,6 @@ class PlayerListenerTest : AbstractKoinTest() { assertEquals(0, clientManager.clients.size) assertFalse { client.isActive } - coVerify { scoreboardManagerMock.remove(player) } - coVerify { languageManager.remove(player) } } private fun createEvent(player: Player): PlayerQuitEvent { diff --git a/src/test/kotlin/com/github/rushyverse/api/listener/api/LanguageListenerTest.kt b/src/test/kotlin/com/github/rushyverse/api/listener/api/LanguageListenerTest.kt new file mode 100644 index 00000000..1e298586 --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/listener/api/LanguageListenerTest.kt @@ -0,0 +1,39 @@ +package com.github.rushyverse.api.listener.api + +import com.github.rushyverse.api.AbstractKoinTest +import com.github.rushyverse.api.player.language.LanguageManager +import io.mockk.coJustRun +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import net.kyori.adventure.text.Component +import org.bukkit.entity.Player +import org.bukkit.event.player.PlayerQuitEvent +import kotlin.test.BeforeTest +import kotlin.test.Test + +class LanguageListenerTest: AbstractKoinTest() { + + private lateinit var listener: LanguageListener + private lateinit var languageManager: LanguageManager + + @BeforeTest + override fun onBefore() { + super.onBefore() + listener = LanguageListener() + languageManager = mockk() + + loadApiTestModule { + single { languageManager } + } + } + + @Test + fun `should call remove in manager when player leave`() = runTest { + val player = mockk() + coJustRun { languageManager.remove(player) } + listener.onQuit(PlayerQuitEvent(player, Component.empty(), PlayerQuitEvent.QuitReason.DISCONNECTED)) + coVerify(exactly = 1) { languageManager.remove(player) } + } + +} diff --git a/src/test/kotlin/com/github/rushyverse/api/listener/api/ScoreboardListenerTest.kt b/src/test/kotlin/com/github/rushyverse/api/listener/api/ScoreboardListenerTest.kt new file mode 100644 index 00000000..7123f153 --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/listener/api/ScoreboardListenerTest.kt @@ -0,0 +1,39 @@ +package com.github.rushyverse.api.listener.api + +import com.github.rushyverse.api.AbstractKoinTest +import com.github.rushyverse.api.player.scoreboard.ScoreboardManager +import io.mockk.coJustRun +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import net.kyori.adventure.text.Component +import org.bukkit.entity.Player +import org.bukkit.event.player.PlayerQuitEvent +import kotlin.test.BeforeTest +import kotlin.test.Test + +class ScoreboardListenerTest: AbstractKoinTest() { + + private lateinit var listener: ScoreboardListener + private lateinit var scoreboardManager: ScoreboardManager + + @BeforeTest + override fun onBefore() { + super.onBefore() + listener = ScoreboardListener() + scoreboardManager = mockk() + + loadApiTestModule { + single { scoreboardManager } + } + } + + @Test + fun `should call remove in manager when player leave`() = runTest { + val player = mockk() + coJustRun { scoreboardManager.remove(player) } + listener.onQuit(PlayerQuitEvent(player, Component.empty(), PlayerQuitEvent.QuitReason.DISCONNECTED)) + coVerify(exactly = 1) { scoreboardManager.remove(player) } + } + +} From 71fb359cb31f2fa11a5c0f1891171934e70ecc11 Mon Sep 17 00:00:00 2001 From: Distractic <46402441+Distractic@users.noreply.github.com> Date: Tue, 15 Aug 2023 10:55:33 +0200 Subject: [PATCH 137/143] feat: Add time format (#73) Co-authored-by: Cizetux --- build.gradle.kts | 4 + config/detekt/detekt.yml | 4 +- .../rushyverse/api/extension/_Duration.kt | 119 +++ .../rushyverse/api/extension/_String.kt | 25 + .../github/rushyverse/api/time/FormatTime.kt | 221 ++++++ src/main/resources/api_translate.properties | 12 + .../resources/api_translate_de_DE.properties | 21 + .../resources/api_translate_en_GB.properties | 21 + .../resources/api_translate_es_ES.properties | 21 + .../resources/api_translate_fr_FR.properties | 10 +- .../resources/api_translate_zh_CN.properties | 21 + .../api/extension/DurationExtTest.kt | 733 ++++++++++++++++++ .../rushyverse/api/extension/StringExtTest.kt | 36 + ...roperties => test_bundle_zh_CN.properties} | 0 14 files changed, 1245 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/com/github/rushyverse/api/time/FormatTime.kt create mode 100644 src/main/resources/api_translate_de_DE.properties create mode 100644 src/main/resources/api_translate_zh_CN.properties rename src/test/resources/{test_bundle_zh_CH.properties => test_bundle_zh_CN.properties} (100%) diff --git a/build.gradle.kts b/build.gradle.kts index 2d566328..0a4ec9ec 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -103,6 +103,10 @@ kotlin { jvmToolchain(javaVersionInt) sourceSets { + compilerOptions { + freeCompilerArgs = listOf("-Xcontext-receivers") + } + all { languageSettings { optIn("kotlin.RequiresOptIn") diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index 9c6a77e8..53f2cf19 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -347,7 +347,7 @@ naming: active: true parameterPattern: '[a-z][A-Za-z0-9]*|_' MatchingDeclarationName: - active: true + active: false mustBeFirst: true MemberNameEqualsClassName: active: true @@ -681,7 +681,7 @@ style: SpacingBetweenPackageAndImports: active: true StringShouldBeRawString: - active: true + active: false maxEscapedCharacterCount: 2 ignoredCharacters: [ ] ThrowsCount: diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Duration.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Duration.kt index 948e92f6..454007d5 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_Duration.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Duration.kt @@ -1,8 +1,25 @@ package com.github.rushyverse.api.extension +import com.github.rushyverse.api.time.FormatTime +import com.github.rushyverse.api.time.INFINITE_SYMBOL import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds +/** + * Number of hours in a day. + */ +public const val HOUR_IN_DAY: Int = 24 + +/** + * Number of minutes in an hour. + */ +public const val MINUTE_IN_HOUR: Int = 60 + +/** + * Number of seconds in a minute. + */ +public const val SECOND_IN_MINUTE: Int = 60 + /** * Number of milliseconds corresponding to one tick. */ @@ -43,3 +60,105 @@ public val UShort.ticks: Duration get() = toShort().ticks * 1 tick corresponding to 50 milliseconds, 20 ticks to 1 second. */ public val Short.ticks: Duration get() = (this * MILLISECOND_PER_TICK).milliseconds + +/** + * Format a [Duration] to a string. + * + * If the duration is infinite, the [infiniteSymbol] will be used, for example `∞h ∞m ∞s`. + * + * Example: + * ```kotlin + * (1.hours + 2.minutes + 3.seconds).format(..) // 01h 02m 03s + * (2.minutes + 3.seconds).format(..) // 02m 03s + * (3.seconds).format(..) // 03s + * ``` + * + * @receiver Duration The duration to format. + * @param format The format to use. + * @param separator Use to separate the hour, minute and second + * @param infiniteSymbol Symbol to use when the duration is infinite. + * @return Formatted string. + */ +public fun Duration.format( + format: FormatTime, + separator: String = " ", + infiniteSymbol: String = INFINITE_SYMBOL +): String { + require(!this.isNegative()) { "Number must be positive" } + return buildString { + if (isInfinite()) { + formatInfiniteTime(format, separator, infiniteSymbol) + } else { + formatTime(format, this@format, separator) + } + + if (endsWith(separator)) { + deleteLast(separator.length) + } + } +} + +/** + * Format a [Duration] to a string. + * + * Example: + * ```kotlin + * formatTime(..) // 10d 01h 02m 03s + * ``` + * @receiver The string builder to append the formatted string. + * @param format The format to use. + * @param separator Use to separate the hour, minute and second + * @return Formatted string. + */ +private fun StringBuilder.formatTime( + format: FormatTime, + duration: Duration, + separator: String = " " +) { + var isFirstUnit = true + format.getDay(duration)?.let { + append(it) + append(separator) + isFirstUnit = false + } + + format.getHour(duration, isFirstUnit)?.let { + append(it) + append(separator) + isFirstUnit = false + } + + format.getMinute(duration, isFirstUnit)?.let { + append(it) + append(separator) + isFirstUnit = false + } + + format.getSecond(duration, isFirstUnit)?.let { + append(it) + } +} + +/** + * Format an infinite time to a string. + * + * Example: + * ```kotlin + * formatInfiniteTime(..) // ∞d ∞h ∞m ∞s + * ``` + * @receiver The string builder to append the formatted string. + * @param format The format to use. + * @param separator Use to separate the hour, minute and second + * @param infiniteSymbol Symbol to use when the duration is infinite. + * @return Formatted string. + */ +private fun StringBuilder.formatInfiniteTime( + format: FormatTime, + separator: String = " ", + infiniteSymbol: String = INFINITE_SYMBOL +) { + format.day?.let { append(it(infiniteSymbol)).append(separator) } + format.hour?.let { append(it(infiniteSymbol)).append(separator) } + format.minute?.let { append(it(infiniteSymbol)).append(separator) } + format.second?.let { append(it(infiniteSymbol)) } +} diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt index e723f542..59523834 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_String.kt @@ -46,6 +46,14 @@ private const val UUID_HIGH_LOW_BITS: Int = 64 */ public const val DEFAULT_LORE_LINE_LENGTH: Int = 30 +/** + * Converts the receiver String to Int if possible, otherwise returns the same String. + * + * @receiver The input String. + * @return The converted Int value if successful or the original String itself if not. + */ +public fun String.toIntOrString(): Any = toIntOrNull() ?: this + /** * Wraps a given string with a color tag. * Example: "Hello".wrapColorWith("red") will return `Hello`. @@ -238,3 +246,20 @@ public fun String.asComponent( vararg tagResolver: TagResolver, miniMessage: MiniMessage = MINI_MESSAGE_NON_STRICT, ): Component = miniMessage.deserialize(this, *tagResolver) + +/** + * Deletes the last [size] characters from the `StringBuilder`. + * + * @receiver the [StringBuilder] to delete from. + * @param size the number of characters to delete. + * @return the modified [StringBuilder] after deleting the characters. + * @throws IllegalArgumentException if [StringBuilder] is negative. + */ +public fun StringBuilder.deleteLast(size: Int): StringBuilder { + return when { + size < 0 -> throw IllegalArgumentException("Size must be positive, but was $size") + size == 0 -> this + size >= length -> clear() + else -> delete(length - size, length) + } +} diff --git a/src/main/kotlin/com/github/rushyverse/api/time/FormatTime.kt b/src/main/kotlin/com/github/rushyverse/api/time/FormatTime.kt new file mode 100644 index 00000000..41f517ad --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/time/FormatTime.kt @@ -0,0 +1,221 @@ +package com.github.rushyverse.api.time + +import com.github.rushyverse.api.APIPlugin +import com.github.rushyverse.api.extension.HOUR_IN_DAY +import com.github.rushyverse.api.extension.MINUTE_IN_HOUR +import com.github.rushyverse.api.extension.SECOND_IN_MINUTE +import com.github.rushyverse.api.extension.toIntOrString +import com.github.rushyverse.api.translation.Translator +import java.util.* +import kotlin.time.Duration + +/** + * String used to represent an infinite duration. + */ +public const val INFINITE_SYMBOL: String = "∞" + +/** + * Data class that represents a time format. + * It provides properties and companion functions to create time formats. + * + * @property second A function that formats the seconds. + * @property minute A function that formats the minutes. + * @property hour A function that formats the hours. + * @property day A function that formats the days. + * @property prefixSingleDigitWithZero Whether to prefix single digit time units with a zero. + * @property acceptZero Whether to accept zero as a value for the time units. + * If false, the time unit will not be displayed if the value is zero. + * @property beginAtZero Whether to begin the time format at the first non-zero time unit. + * @constructor Creates a new instance of FormatTime. + */ +public data class FormatTime( + val second: FormatPartTime? = null, + val minute: FormatPartTime? = null, + val hour: FormatPartTime? = null, + val day: FormatPartTime? = null, + val prefixSingleDigitWithZero: Boolean = DEFAULT_PREFIX_SINGLE_DIGIT_WITH_ZERO, + val acceptZero: Boolean = DEFAULT_ACCEPT_ZERO, + val beginAtZero: Boolean = DEFAULT_BEGIN_AT_ZERO +) { + + public companion object { + + private const val DEFAULT_PREFIX_SINGLE_DIGIT_WITH_ZERO = true + + private const val DEFAULT_ACCEPT_ZERO = true + + private const val DEFAULT_BEGIN_AT_ZERO = false + + private const val ZERO = "0" + + /** + * Regex used to get the number in a string. + * Will create groups named `text1`, `number` and `text2` to get the text before and after the number. + * The number found has to be a single digit. + */ + private val patternTranslationWithSingleDigitTime = Regex("^(?\\D*)(?\\d)(?\\D*)$") + + /** + * Constructs a FormatTime object for displaying time in long format. + * The long format is used for displaying time in a more readable way. + * @param translator The translator used for retrieving translations. + * @param locale The desired locale for the translations. + * @param bundle The name of the translation bundle to use. + * @property prefixSingleDigitWithZero Whether to prefix single digit time units with a zero. + * @property acceptZero Whether to accept zero as a value for the time units. + * If false, the time unit will not be displayed if the value is zero. + * @property beginAtZero Whether to begin the time format at the first non-zero time unit. + * @return A FormatTime object configured for long format. + */ + public fun long( + translator: Translator, + locale: Locale, + bundle: String = APIPlugin.BUNDLE_API, + prefixSingleDigitWithZero: Boolean = DEFAULT_PREFIX_SINGLE_DIGIT_WITH_ZERO, + acceptZero: Boolean = DEFAULT_ACCEPT_ZERO, + beginAtZero: Boolean = DEFAULT_BEGIN_AT_ZERO + ): FormatTime = FormatTime( + second = { translator.get("time.second.long", locale, arrayOf(it.toIntOrString()), bundle) }, + minute = { translator.get("time.minute.long", locale, arrayOf(it.toIntOrString()), bundle) }, + hour = { translator.get("time.hour.long", locale, arrayOf(it.toIntOrString()), bundle) }, + day = { translator.get("time.day.long", locale, arrayOf(it.toIntOrString()), bundle) }, + prefixSingleDigitWithZero = prefixSingleDigitWithZero, + acceptZero = acceptZero, + beginAtZero = beginAtZero + ) + + /** + * Constructs a FormatTime object for displaying time in short format. + * The short format is used for displaying time in a more compact way. + * @param translator The translator used for retrieving translations. + * @param locale The desired locale for the translations. + * @param bundle The name of the translation bundle to use. + * @property prefixSingleDigitWithZero Whether to prefix single digit time units with a zero. + * @property acceptZero Whether to accept zero as a value for the time units. + * If false, the time unit will not be displayed if the value is zero. + * @property beginAtZero Whether to begin the time format at the first non-zero time unit. + * @return A FormatTime object configured for short format. + */ + public fun short( + translator: Translator, + locale: Locale, + bundle: String = APIPlugin.BUNDLE_API, + prefixSingleDigitWithZero: Boolean = DEFAULT_PREFIX_SINGLE_DIGIT_WITH_ZERO, + acceptZero: Boolean = DEFAULT_ACCEPT_ZERO, + beginAtZero: Boolean = DEFAULT_BEGIN_AT_ZERO + ): FormatTime = FormatTime( + second = { translator.get("time.second.short", locale, arrayOf(it.toIntOrString()), bundle) }, + minute = { translator.get("time.minute.short", locale, arrayOf(it.toIntOrString()), bundle) }, + hour = { translator.get("time.hour.short", locale, arrayOf(it.toIntOrString()), bundle) }, + day = { translator.get("time.day.short", locale, arrayOf(it.toIntOrString()), bundle) }, + prefixSingleDigitWithZero = prefixSingleDigitWithZero, + acceptZero = acceptZero, + beginAtZero = beginAtZero + ) + } + + /** + * Get the day formatted time for the given duration. + * @param duration The duration to format. + * @return The formatted time if [day] is not null, null otherwise. + */ + public fun getDay(duration: Duration): String? { + day?.let { + val value = duration.inWholeDays + return getTimeOrZeroFormatted(it, value, true) + } + return null + } + + /** + * Get the hour formatted time for the given duration. + * @param duration The duration to format. + * @param isFirstUnit Whether the hour is the first unit to be displayed. + * @return The formatted time if [hour] is not null, null otherwise. + */ + public fun getHour(duration: Duration, isFirstUnit: Boolean): String? { + hour?.let { + val value = if (isFirstUnit) duration.inWholeHours else duration.inWholeHours % HOUR_IN_DAY + return getTimeOrZeroFormatted(it, value, isFirstUnit) + } + return null + } + + /** + * Get the minute formatted time for the given duration. + * @param duration The duration to format. + * @param isFirstUnit Whether the minute is the first unit to be displayed. + * @return The formatted time if [minute] is not null, null otherwise. + */ + public fun getMinute(duration: Duration, isFirstUnit: Boolean): String? { + minute?.let { + val value = if (isFirstUnit) duration.inWholeMinutes else duration.inWholeMinutes % MINUTE_IN_HOUR + return getTimeOrZeroFormatted(it, value, isFirstUnit) + } + return null + } + + /** + * Get the second formatted time for the given duration. + * @param duration The duration to format. + * @param isFirstUnit Whether the second is the first unit to be displayed. + * @return The formatted time if [second] is not null, null otherwise. + */ + public fun getSecond(duration: Duration, isFirstUnit: Boolean): String? { + second?.let { + val value = if (isFirstUnit) duration.inWholeSeconds else duration.inWholeSeconds % SECOND_IN_MINUTE + return getTimeOrZeroFormatted(it, value, isFirstUnit) + } + return null + } + + /** + * Get the formatted time for the given duration. + * If [time] is greater than 0, the formatted time will be the formatted [time]. + * If [time] is 0, and it is the first unit to be displayed, the formatted time will be 0. + * If [time] is 0, and [acceptZero] is true, the formatted time will be 0. + * @param format Format to use. + * @param time Time to format. + * @param isFirstUnit Whether the time is the first unit to be displayed. + * @return The formatted time if [format] is not null, null otherwise. + */ + private fun getTimeOrZeroFormatted(format: FormatPartTime, time: Long, isFirstUnit: Boolean): String? { + return when { + time > 0 -> time.toString() + isFirstUnit -> if (beginAtZero && acceptZero) ZERO else null + acceptZero -> ZERO + else -> null + }?.let { adapt(format(it)) } + } + + /** + * Adapts a string to the format according to the object configuration. + * @param string Value to adapt. + * @return The adapted string, can be the same as the input string. + */ + private fun adapt(string: String): String { + return if (prefixSingleDigitWithZero) { + prefixSingleDigitWithZero(string) + } else { + string + } + } + + /** + * Adds a prefix zero for single-digit numbers in a given string. + * + * @param string The input string. + * @return The modified string with prefix zero for single-digit numbers. + */ + private fun prefixSingleDigitWithZero(string: String): String { + return string.replace(patternTranslationWithSingleDigitTime) { matchResult -> + val (text1, number, text2) = matchResult.destructured + "${text1}0${number}${text2}" + } + } +} + +/** + * Type of function used to format a [Duration] part to a string. + */ +public typealias FormatPartTime = (String) -> String diff --git a/src/main/resources/api_translate.properties b/src/main/resources/api_translate.properties index da735fea..786403e2 100644 --- a/src/main/resources/api_translate.properties +++ b/src/main/resources/api_translate.properties @@ -7,3 +7,15 @@ team.purple=Purple team.aqua=Aqua team.black=Black player.join.team={0} joined {1} team +time.day.short={0}d +time.hour.short={0}h +time.minute.short={0}m +time.second.short={0}s +time.day.long={0} {0, plural, one {day} other {days}} +time.hour.long={0} {0, plural, one {hour} other {hours}} +time.minute.long={0} {0, plural, one {minute} other {minutes}} +time.second.long={0} {0, plural, one {second} other {seconds}} +team.blue.member=Blue +team.green.member=Green +team.purple.member=Purple +team.white.member=White diff --git a/src/main/resources/api_translate_de_DE.properties b/src/main/resources/api_translate_de_DE.properties new file mode 100644 index 00000000..57cd4d0d --- /dev/null +++ b/src/main/resources/api_translate_de_DE.properties @@ -0,0 +1,21 @@ +team.blue.member=Blau +team.green.member=Grün +team.green=Grün +team.yellow=Gelb +team.blue=Blau +team.red=Rot +team.purple=Lila +team.aqua=Aqua +team.black=Schwarz +team.white.member=Weiß +team.white=Weiß +team.purple.member=Lila +time.day.short={0}T +time.hour.short={0}S +time.minute.short={0}min +time.second.short={0}s +time.day.long={0} {0, plural, one {Tag} other {Tage}} +time.hour.long={0} {0, plural, one {Stunde} other {Stunden}} +time.minute.long={0} {0, plural, one {Minute} other {Minuten}} +time.second.long={0} {0, plural, one {Sekunde} other {Sekunden}} +player.join.team={0} ist dem Team {1} beigetreten \ No newline at end of file diff --git a/src/main/resources/api_translate_en_GB.properties b/src/main/resources/api_translate_en_GB.properties index e69de29b..0e9bc610 100644 --- a/src/main/resources/api_translate_en_GB.properties +++ b/src/main/resources/api_translate_en_GB.properties @@ -0,0 +1,21 @@ +time.day.long={0} {0, plural, one {day} other {days}} +time.day.short={0}d +time.hour.short={0}h +time.minute.short={0}m +time.second.short={0}s +time.hour.long={0} {0, plural, one {hour} other {hours}} +time.minute.long={0} {0, plural, one {minute} other {minutes}} +time.second.long={0} {0, plural, one {second} other {seconds}} +team.red=Red +team.white=White +team.blue=Blue +team.green=Green +team.yellow=Yellow +team.purple=Purple +team.aqua=Aqua +team.black=Black +player.join.team={0} joined {1} team +team.blue.member=Blue +team.green.member=Green +team.purple.member=Purple +team.white.member=White diff --git a/src/main/resources/api_translate_es_ES.properties b/src/main/resources/api_translate_es_ES.properties index e69de29b..64a0f3ec 100644 --- a/src/main/resources/api_translate_es_ES.properties +++ b/src/main/resources/api_translate_es_ES.properties @@ -0,0 +1,21 @@ +time.day.short={0}d +time.hour.short={0}h +time.minute.short={0}m +time.second.short={0}s +time.day.long={0} {0, plural, one {día} other {días}} +time.hour.long={0} {0, plural, one {hora} other {horas}} +time.minute.long={0} {0, plural, one {minuto} other {minutos}} +time.second.long={0} {0, plural, one {segundo} other {segundos}} +team.black=Negro +team.blue.member=Azul +team.blue=Azul +team.green=Verde +team.green.member=Verde +team.white.member=Blanco +team.white=Blanco +team.red=Rojo +team.yellow=Amarillo +team.aqua=Agua +team.purple=Violeta +team.purple.member=Violeta +player.join.team={0} se unió al equipo {1} diff --git a/src/main/resources/api_translate_fr_FR.properties b/src/main/resources/api_translate_fr_FR.properties index c3784d13..4b147315 100644 --- a/src/main/resources/api_translate_fr_FR.properties +++ b/src/main/resources/api_translate_fr_FR.properties @@ -10,4 +10,12 @@ team.purple=Violette team.purple.member=Violet team.aqua=Aqua team.black=Noire -player.join.team={0} a rejoint l'quipe {1} +player.join.team={0} a rejoint l'équipe {1} +time.day.short={0}j +time.hour.short={0}h +time.minute.short={0}m +time.second.short={0}s +time.day.long={0} {0, plural, zero {jour} one {jour} other {jours}} +time.hour.long={0} {0, plural, zero {heure} one {heure} other {heures}} +time.minute.long={0} {0, plural, zero {minute} one {minute} other {minutes}} +time.second.long={0} {0, plural, zero {seconde} one {seconde} other {secondes}} diff --git a/src/main/resources/api_translate_zh_CN.properties b/src/main/resources/api_translate_zh_CN.properties new file mode 100644 index 00000000..343d223b --- /dev/null +++ b/src/main/resources/api_translate_zh_CN.properties @@ -0,0 +1,21 @@ +time.day.short={0}天 +time.hour.short={0}小时 +time.minute.short={0}分 +time.second.short={0}秒 +time.day.long={0}天 +time.hour.long={0}小时 +time.minute.long={0}分 +time.second.long={0}秒 +team.white.member=白 +team.purple.member=紫 +team.blue.member=蓝 +team.green.member=绿 +team.aqua=青队 +team.black=黑队 +team.purple=紫队 +team.yellow=黄队 +team.blue=蓝队 +team.red=红队 +team.white=白队 +team.green=绿队 +player.join.team={0} 加入了 {1} 的团队 diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/DurationExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/DurationExtTest.kt index cbedeab7..ea9bd37d 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/DurationExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/DurationExtTest.kt @@ -1,11 +1,24 @@ package com.github.rushyverse.api.extension +import com.github.rushyverse.api.APIPlugin +import com.github.rushyverse.api.time.FormatTime +import com.github.rushyverse.api.translation.ResourceBundleTranslator +import com.github.rushyverse.api.translation.SupportedLanguage +import com.github.rushyverse.api.translation.registerResourceBundleForSupportedLocales import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import java.util.* +import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.time.Duration +import kotlin.time.Duration.Companion.days +import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds class DurationExtTest { @@ -111,4 +124,724 @@ class DurationExtTest { assertEquals(250.milliseconds, 5.toULong().ticks) } } + + @Nested + inner class LongFormat { + + private lateinit var translator: ResourceBundleTranslator + + @BeforeTest + fun onBefore() { + translator = ResourceBundleTranslator(APIPlugin.BUNDLE_API) + translator.registerResourceBundleForSupportedLocales(APIPlugin.BUNDLE_API, ResourceBundle::getBundle) + } + + @Nested + inner class French { + + private val locale = SupportedLanguage.FRENCH.locale + + @ParameterizedTest + @ValueSource(ints = [0]) + fun `should return the correct format for 0 second`(time: Int) { + assertEquals( + "", time.seconds.format( + FormatTime.long(translator, locale) + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [1]) + fun `should return the correct format for singular value second`(time: Int) { + assertEquals("0${time} seconde", time.seconds.format(FormatTime.long(translator, locale))) + } + + @ParameterizedTest + @ValueSource(ints = [2, 3, 4, 5, 6, 7, 8, 9]) + fun `should return the correct format for plural value with single digit`(time: Int) { + assertEquals("0${time} secondes", time.seconds.format(FormatTime.long(translator, locale))) + } + + @ParameterizedTest + @ValueSource(ints = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + fun `should return the correct format for plural value with double digit`(time: Int) { + assertEquals("${time} secondes", time.seconds.format(FormatTime.long(translator, locale))) + } + + @ParameterizedTest + @ValueSource(ints = [1]) + fun `should return the correct format for singular value minute`(time: Int) { + assertEquals( + "0${time} minute 00 seconde", time.minutes.format( + FormatTime.long(translator, locale, acceptZero = true) + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [2, 3, 4, 5, 6, 7, 8, 9]) + fun `should return the correct format for plural value with single digit minute`(time: Int) { + assertEquals( + "0${time} minutes 00 seconde", time.minutes.format( + FormatTime.long(translator, locale, acceptZero = true) + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + fun `should return the correct format for plural value with double digit minute`(time: Int) { + assertEquals( + "$time minutes 00 seconde", time.minutes.format( + FormatTime.long(translator, locale, acceptZero = true) + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [1]) + fun `should return the correct format for singular value hour`(time: Int) { + assertEquals( + "0${time} heure 00 minute 00 seconde", time.hours.format( + FormatTime.long(translator, locale, acceptZero = true) + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [2, 3, 4, 5, 6, 7, 8, 9]) + fun `should return the correct format for plural value with single digit hour`(time: Int) { + assertEquals( + "0${time} heures 00 minute 00 seconde", + time.hours.format(FormatTime.long(translator, locale, acceptZero = true)) + ) + } + + @ParameterizedTest + @ValueSource(ints = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + fun `should return the correct format for plural value with double digit hour`(time: Int) { + assertEquals( + "$time heures 00 minute 00 seconde", time.hours.format( + FormatTime.long(translator, locale, acceptZero = true) + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [1]) + fun `should return the correct format for singular value day`(time: Int) { + assertEquals( + "0${time} jour 00 heure 00 minute 00 seconde", + time.days.format(FormatTime.long(translator, locale, acceptZero = true)) + ) + } + + @ParameterizedTest + @ValueSource(ints = [2, 3, 4, 5, 6, 7, 8, 9]) + fun `should return the correct format for plural value with single digit day`(time: Int) { + assertEquals( + "0${time} jours 00 heure 00 minute 00 seconde", + time.days.format(FormatTime.long(translator, locale, acceptZero = true)) + ) + } + + @ParameterizedTest + @ValueSource(ints = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + fun `should return the correct format for plural value with double digit day`(time: Int) { + assertEquals( + "$time jours 00 heure 00 minute 00 seconde", + time.days.format(FormatTime.long(translator, locale, acceptZero = true)) + ) + } + + @Test + fun `should return the correct format for multiple values`() { + assertEquals( + "04 jours 01 heure 02 minutes 03 secondes", + (4.days + 1.hours + 2.minutes + 3.seconds).format( + FormatTime.long(translator, locale, acceptZero = true) + ) + ) + } + + } + + @Nested + inner class English { + + private val locale = SupportedLanguage.ENGLISH.locale + + @ParameterizedTest + @ValueSource(ints = [0]) + fun `should return the correct format for 0 second`(time: Int) { + assertEquals( + "", time.seconds.format( + FormatTime.long(translator, locale) + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [1]) + fun `should return the correct format for singular value second`(time: Int) { + assertEquals("0${time} second", time.seconds.format(FormatTime.long(translator, locale))) + } + + @ParameterizedTest + @ValueSource(ints = [2, 3, 4, 5, 6, 7, 8, 9]) + fun `should return the correct format for plural value with single digit`(time: Int) { + assertEquals( + "0${time} seconds", time.seconds.format( + FormatTime.long(translator, locale, acceptZero = true) + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + fun `should return the correct format for plural value with double digit`(time: Int) { + assertEquals( + "${time} seconds", time.seconds.format( + FormatTime.long(translator, locale, acceptZero = true) + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [1]) + fun `should return the correct format for singular value minute`(time: Int) { + assertEquals( + "0${time} minute 00 seconds", time.minutes.format( + FormatTime.long(translator, locale, acceptZero = true) + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [2, 3, 4, 5, 6, 7, 8, 9]) + fun `should return the correct format for plural value with single digit minute`(time: Int) { + assertEquals( + "0${time} minutes 00 seconds", time.minutes.format( + FormatTime.long(translator, locale, acceptZero = true) + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + fun `should return the correct format for plural value with double digit minute`(time: Int) { + assertEquals( + "$time minutes 00 seconds", time.minutes.format( + FormatTime.long(translator, locale, acceptZero = true) + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [1]) + fun `should return the correct format for singular value hour`(time: Int) { + assertEquals( + "0${time} hour 00 minutes 00 seconds", time.hours.format( + FormatTime.long(translator, locale, acceptZero = true) + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [2, 3, 4, 5, 6, 7, 8, 9]) + fun `should return the correct format for plural value with single digit hour`(time: Int) { + assertEquals( + "0${time} hours 00 minutes 00 seconds", time.hours.format( + FormatTime.long(translator, locale, acceptZero = true) + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + fun `should return the correct format for plural value with double digit hour`(time: Int) { + assertEquals( + "$time hours 00 minutes 00 seconds", time.hours.format( + FormatTime.long(translator, locale, acceptZero = true) + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [1]) + fun `should return the correct format for singular value day`(time: Int) { + assertEquals( + "0${time} day 00 hours 00 minutes 00 seconds", time.days.format( + FormatTime.long(translator, locale, acceptZero = true) + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [2, 3, 4, 5, 6, 7, 8, 9]) + fun `should return the correct format for plural value with single digit day`(time: Int) { + assertEquals( + "0${time} days 00 hours 00 minutes 00 seconds", time.days.format( + FormatTime.long(translator, locale, acceptZero = true) + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + fun `should return the correct format for plural value with double digit day`(time: Int) { + assertEquals( + "$time days 00 hours 00 minutes 00 seconds", time.days.format( + FormatTime.long(translator, locale, acceptZero = true) + ) + ) + } + + @Test + fun `should return the correct format for multiple values`() { + assertEquals( + "04 days 01 hour 02 minutes 03 seconds", + (4.days + 1.hours + 2.minutes + 3.seconds).format( + FormatTime.long(translator, locale, acceptZero = true) + ) + ) + } + } + } + + @Nested + inner class ShortFormat { + + private lateinit var translator: ResourceBundleTranslator + + @BeforeTest + fun onBefore() { + translator = ResourceBundleTranslator(APIPlugin.BUNDLE_API) + translator.registerResourceBundleForSupportedLocales(APIPlugin.BUNDLE_API, ResourceBundle::getBundle) + } + + @Test + fun `should throw an exception if the duration is negative`() { + assertThrows { + (-1).seconds.format( + FormatTime.short( + translator, + SupportedLanguage.CHINESE.locale + ) + ) + } + } + + @ParameterizedTest + @ValueSource(strings = ["", " ", "-"]) + fun `should use separator between the different parts`(separator: String) { + val time = (4.days + 1.hours + 2.minutes + 3.seconds).format( + FormatTime.short( + translator, + SupportedLanguage.ENGLISH.locale + ), + separator = separator + ) + assertEquals( + "04d${separator}01h${separator}02m${separator}03s", + time + ) + } + + @ParameterizedTest + @ValueSource(strings = ["∞", "inf"]) + fun `should return infinity if the duration is infinite`(infinity: String) { + assertEquals( + "${infinity}d ${infinity}h ${infinity}m ${infinity}s", + Duration.INFINITE.format( + FormatTime.short( + translator, + SupportedLanguage.ENGLISH.locale + ), + infiniteSymbol = infinity + ) + ) + } + + @Test + fun `should return the correct format for 0`() { + assertEquals( + "", Duration.ZERO.format( + FormatTime.short( + translator, + SupportedLanguage.ENGLISH.locale, + acceptZero = true + ) + ) + ) + } + + @Test + fun `should return the correct format for 1 minute`() { + assertEquals( + "01m 00s", 1.minutes.format( + FormatTime.short( + translator, + SupportedLanguage.ENGLISH.locale, + acceptZero = true + ) + ) + ) + } + + @Test + fun `should return the correct format for 1 hour`() { + assertEquals( + "01h 00m 00s", 1.hours.format( + FormatTime.short( + translator, + SupportedLanguage.ENGLISH.locale, + acceptZero = true + ) + ) + ) + } + + @Test + fun `should return the correct format for 1 hour 1 minute`() { + assertEquals( + "01h 01m 00s", (1.hours + 1.minutes).format( + FormatTime.short( + translator, + SupportedLanguage.ENGLISH.locale, + acceptZero = true + ) + ) + ) + } + + @Test + fun `should use selected language`() { + assertEquals( + "04天 12小时 38分 01秒", (4.days + 12.hours + 38.minutes + 1.seconds).format( + FormatTime.short( + translator, + SupportedLanguage.CHINESE.locale + ) + ) + ) + } + + } + + @Nested + inner class TimeFormat { + + private val format: FormatTime = FormatTime( + second = { it + "s" }, + minute = { it + "m" }, + hour = { it + "h" }, + day = { it + "d" }, + ) + + @Nested + inner class PrefixSingleDigitWithZero { + + private val localFormat = format.copy( + beginAtZero = true, + acceptZero = true, + prefixSingleDigitWithZero = true + ) + + @ParameterizedTest + @ValueSource(ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + fun `should prefix single digit for seconds`(time: Int) { + assertEquals( + "00d 00h 00m 0${time}s", time.seconds.format( + localFormat + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + fun `should not prefix double digit for seconds`(time: Int) { + assertEquals( + "00d 00h 00m ${time}s", time.seconds.format( + localFormat + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + fun `should prefix single digit for minutes`(time: Int) { + assertEquals( + "00d 00h 0${time}m 00s", time.minutes.format( + localFormat + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + fun `should not prefix double digit for minutes`(time: Int) { + assertEquals( + "00d 00h ${time}m 00s", time.minutes.format( + localFormat + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + fun `should prefix single digit for hours`(time: Int) { + assertEquals( + "00d 0${time}h 00m 00s", time.hours.format( + localFormat + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + fun `should not prefix double digit for hours`(time: Int) { + assertEquals( + "00d ${time}h 00m 00s", time.hours.format( + localFormat + ) + ) + } + + @ParameterizedTest + @ValueSource(ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + fun `should prefix single digit for days`(time: Int) { + assertEquals( + "0${time}d 00h 00m 00s", time.days.format( + localFormat + ) + ) + } + + @Test + fun `should prefix for all time`() { + assertEquals( + "01d 01h 01m 01s", (1.days + 1.hours + 1.minutes + 1.seconds).format( + localFormat + ) + ) + } + + } + + @Nested + inner class AcceptZero { + + private val localFormat = format.copy( + beginAtZero = false, + acceptZero = true, + prefixSingleDigitWithZero = false + ) + + @Test + fun `should display all time`() { + assertEquals( + "", Duration.ZERO.format( + localFormat + ) + ) + } + + @Test + fun `should not display day if there is only hour`() { + assertEquals( + "1h 0m 0s", 1.hours.format( + localFormat + ) + ) + } + + @Test + fun `should not display 0 value`() { + assertEquals( + "1h 1s", (1.hours + 1.seconds).format( + localFormat.copy( + beginAtZero = false, + acceptZero = false + ) + ) + ) + } + + @Test + fun `should not display day if day format is null`() { + assertEquals( + "24h 0m 0s", 1.days.format( + localFormat.copy( + day = null + ) + ) + ) + } + + } + + @Nested + inner class BeginAtZero { + + private val localFormat = format.copy( + beginAtZero = true, + acceptZero = true, + prefixSingleDigitWithZero = false + ) + + @Test + fun `should display all time`() { + assertEquals( + "0d 0h 0m 0s", Duration.ZERO.format( + localFormat + ) + ) + } + + @Test + fun `should not display 0 at begin if zero not accepted`() { + assertEquals( + "1h", 1.hours.format( + localFormat.copy( + acceptZero = false + ) + ) + ) + } + + @Test + fun `should display 0 day if there is only hour`() { + assertEquals( + "0d 1h 0m 0s", 1.hours.format( + localFormat + ) + ) + } + + @Test + fun `should not display day if format is null`() { + assertEquals( + "0h 0m 0s", Duration.ZERO.format( + localFormat.copy( + day = null + ) + ) + ) + } + + @Test + fun `should not display hour if format is null`() { + assertEquals( + "0d 0m 0s", Duration.ZERO.format( + localFormat.copy( + hour = null + ) + ) + ) + } + + @Test + fun `should not display minute if format is null`() { + assertEquals( + "0d 0h 0s", Duration.ZERO.format( + localFormat.copy( + minute = null + ) + ) + ) + } + + @Test + fun `should not display second if format is null`() { + assertEquals( + "0d 0h 0m", Duration.ZERO.format( + localFormat.copy( + second = null + ) + ) + ) + } + + } + + } + + @Nested + inner class FormatInfiniteTime { + + private val format: FormatTime = FormatTime( + second = { it + "s" }, + minute = { it + "m" }, + hour = { it + "h" }, + day = { it + "d" }, + ) + + @Test + fun `should display all time`() { + assertEquals( + "∞d ∞h ∞m ∞s", Duration.INFINITE.format( + format + ) + ) + } + + @Test + fun `should display seconds, minute and hour`() { + assertEquals( + "∞h ∞m ∞s", Duration.INFINITE.format( + format.copy(day = null) + ) + ) + } + + @Test + fun `should display seconds, minute`() { + assertEquals( + "∞m ∞s", Duration.INFINITE.format( + format.copy(day = null, hour = null) + ) + ) + } + + @Test + fun `should display seconds, hour`() { + assertEquals( + "∞h ∞s", Duration.INFINITE.format( + format.copy(day = null, minute = null) + ) + ) + } + + @Test + fun `should display seconds, day`() { + assertEquals( + "∞d ∞s", Duration.INFINITE.format( + format.copy(hour = null, minute = null) + ) + ) + } + + @Test + fun `should display seconds`() { + assertEquals( + "∞s", Duration.INFINITE.format( + format.copy(hour = null, minute = null, day = null) + ) + ) + } + + @ParameterizedTest + @ValueSource(strings = ["∞", "inf"]) + fun `should use separator`(infinity: String) { + assertEquals( + "${infinity}d ${infinity}h ${infinity}m ${infinity}s", + Duration.INFINITE.format( + format, + infiniteSymbol = infinity + ) + ) + } + } } diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt index 78a73efb..89502ada 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/StringExtTest.kt @@ -221,4 +221,40 @@ class StringExtTest { } } + + @Nested + inner class StringBuilderDeleteLast { + + @Test + fun `should throw if size is under 0`() { + assertThrows { + StringBuilder().deleteLast(-1) + } + } + + @Test + fun `should return the same string builder if size is 0`() { + val builder = StringBuilder().append(randomString()) + builder.deleteLast(0) shouldBe builder + } + + + @Test + fun `should return the same string builder if size is bigger than length`() { + val string = randomString() + val builder = StringBuilder().append(string) + builder.deleteLast(string.length) + builder.length shouldBe 0 + } + + @ParameterizedTest + @ValueSource(ints = [1, 2, 3, 4, 5, 6, 7, 8, 9]) + fun `should delete last char`(size: Int) { + val string = randomString() + val builder = StringBuilder().append(string) + builder.deleteLast(size) + builder.toString() shouldBe string.dropLast(size) + } + + } } diff --git a/src/test/resources/test_bundle_zh_CH.properties b/src/test/resources/test_bundle_zh_CN.properties similarity index 100% rename from src/test/resources/test_bundle_zh_CH.properties rename to src/test/resources/test_bundle_zh_CN.properties From 286cad4b4d1c1ba6f8a2a58a27f5faba81e32568 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 19 Aug 2023 17:49:40 +0200 Subject: [PATCH 138/143] doc: Change false documentation --- .../com/github/rushyverse/api/serializer/EnumSerializer.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/EnumSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/EnumSerializer.kt index 0ee31c1a..7b5ff34f 100644 --- a/src/main/kotlin/com/github/rushyverse/api/serializer/EnumSerializer.kt +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/EnumSerializer.kt @@ -37,11 +37,10 @@ public open class EnumSerializer>( * Finds the matching enum value for a given decoded string. * Will transform all spaces to underscore and uppercase all letters. * So for example, "foo bar" will be transformed to "FOO_BAR". - * If no matching enum value is found, an [SerializerException] will be thrown. * * @param decoded The decoded string used to search for the matching enum value. * @return The matching enum value. - * @throws IllegalArgumentException if no matching enum value is found. + * @throws SerializationException if no matching enum value is found. */ public fun findEnumValue(decoded: String): T { val name = decoded.uppercase().replace(" ", "_") From 2a662a2402d01f129ed04dd6938e6edcb33283ef Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 19 Aug 2023 18:39:04 +0200 Subject: [PATCH 139/143] fix: Copy the location instead of modify --- .../kotlin/com/github/rushyverse/api/extension/_Location.kt | 2 +- src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_Location.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_Location.kt index c9a2fc40..94b7a0a2 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_Location.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_Location.kt @@ -55,4 +55,4 @@ public fun Location.divide(value: Number): Location { * @param other The second position. * @return The center position between the two points. */ -public fun Location.centerRelative(other: Location): Location = add(other).divide(2) +public fun Location.centerRelative(other: Location): Location = copy(yaw = 0f, pitch = 0f).add(other).divide(2) diff --git a/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt b/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt index 50e4ea1a..e7865b42 100644 --- a/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt +++ b/src/main/kotlin/com/github/rushyverse/api/world/CubeArea.kt @@ -1,6 +1,7 @@ package com.github.rushyverse.api.world import com.github.rushyverse.api.extension.centerRelative +import com.github.rushyverse.api.extension.copy import com.github.rushyverse.api.extension.minMaxOf import com.github.rushyverse.api.serializer.LocationSerializer import kotlinx.serialization.KSerializer @@ -74,8 +75,8 @@ public class CubeArea(loc1: Location, loc2: Location) : Area { set(value) { // The new position becomes the center of the cube. val halfSize = max.centerRelative(min) - min = value.subtract(halfSize) - max = value.add(halfSize) + min = value.copy().subtract(halfSize) + max = value.copy().add(halfSize) } public var min: Location From 68b80c333674ff38a1e83ea2faf3e87aaf4e238c Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 19 Aug 2023 18:39:27 +0200 Subject: [PATCH 140/143] test: Add for set and get position --- .../api/extension/LocationExtTest.kt | 27 ++++++ .../rushyverse/api/world/cube/CubeAreaTest.kt | 89 +++++++++++++++++++ 2 files changed, 116 insertions(+) diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/LocationExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/LocationExtTest.kt index 1f900e7b..770d850e 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/LocationExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/LocationExtTest.kt @@ -1,6 +1,7 @@ package com.github.rushyverse.api.extension import com.github.rushyverse.api.utils.randomString +import io.kotest.matchers.shouldBe import io.mockk.mockk import org.bukkit.Location import org.bukkit.World @@ -94,4 +95,30 @@ class LocationExtTest { assertEquals(Location(world, x, y, z, yaw, pitch), loc.copy(world, x, y, z, yaw, pitch)) } } + + @Nested + inner class CenterRelative { + + @Test + fun `should return the center position between two points at 0 0 0`() { + loc = Location(loc.world, 0.0, 0.0, 0.0) + val other = Location(loc.world, 0.0, 0.0, 0.0) + val expected = Location(loc.world, 0.0, 0.0, 0.0) + loc.centerRelative(other) shouldBe expected + + other shouldBe Location(loc.world, 0.0, 0.0, 0.0) + loc shouldBe Location(loc.world, 0.0, 0.0, 0.0) + } + + @Test + fun `should return the center position between two points`() { + val previousLoc = loc.copy() + val other = Location(loc.world, 10.0, 10.0, 10.0) + val expected = Location(loc.world, 5.0, 5.5, 6.0) + loc.centerRelative(other) shouldBe expected + + other shouldBe Location(loc.world, 10.0, 10.0, 10.0) + loc shouldBe previousLoc + } + } } diff --git a/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaTest.kt b/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaTest.kt index 454387fe..658c7248 100644 --- a/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/world/cube/CubeAreaTest.kt @@ -175,4 +175,93 @@ class CubeAreaTest { } } + + @Nested + inner class SetPosition { + + @Test + fun `should keep the same position if the new value is the same`() { + val area = CubeArea(Location(worldMock, 0.0, 0.0, 0.0), Location(worldMock, 10.5, 10.5, 10.5)) + val oldMin = area.min + val oldMax = area.max + + area.location = area.location + area.min shouldBe oldMin + area.max shouldBe oldMax + } + + @Test + fun `should change the position if the new positive value is different`() { + val min = Location(worldMock, 0.0, 0.0, 0.0) + val max = Location(worldMock, 10.0, 10.0, 10.0) + val area = CubeArea(min, max) + val newLocation = Location(worldMock, 20.0, 20.0, 20.0) + area.location = newLocation + + area.location shouldBe newLocation + area.min shouldBe Location(worldMock, 15.0, 15.0, 15.0) + area.max shouldBe Location(worldMock, 25.0, 25.0, 25.0) + } + + @Test + fun `should change the position if the new negative value is different`() { + val min = Location(worldMock, 0.0, 0.0, 0.0) + val max = Location(worldMock, 10.0, 10.0, 10.0) + val area = CubeArea(min, max) + val newLocation = Location(worldMock, -20.0, -20.0, -20.0) + area.location = newLocation + area.location shouldBe newLocation + area.min shouldBe Location(worldMock, -25.0, -25.0, -25.0) + area.max shouldBe Location(worldMock, -15.0, -15.0, -15.0) + } + + @Test + fun `should change the position if the new mixed value is different`() { + val min = Location(worldMock, 0.0, 0.0, 0.0) + val max = Location(worldMock, 10.0, 10.0, 10.0) + val area = CubeArea(min, max) + val newLocation = Location(worldMock, 20.0, -20.0, -20.0) + area.location = newLocation + area.location shouldBe newLocation + area.min shouldBe Location(worldMock, 15.0, -25.0, -25.0) + area.max shouldBe Location(worldMock, 25.0, -15.0, -15.0) + } + + } + + @Nested + inner class GetPosition { + + @Test + fun `should return the center of the area with positive values`() { + val min = Location(worldMock, 10.0, 10.0, 10.0) + val max = Location(worldMock, 20.0, 20.0, 20.0) + val area = CubeArea(min, max) + area.location shouldBe Location(worldMock, 15.0, 15.0, 15.0) + } + + @Test + fun `should return the center of the area with negative values`() { + val min = Location(worldMock, -20.0, -20.0, -20.0) + val max = Location(worldMock, -10.0, -10.0, -10.0) + val area = CubeArea(min, max) + area.location shouldBe Location(worldMock, -15.0, -15.0, -15.0) + } + + @Test + fun `should return the center of the area with mixed values`() { + val min = Location(worldMock, -20.0, 10.0, -20.0) + val max = Location(worldMock, -10.0, 20.0, -10.0) + val area = CubeArea(min, max) + area.location shouldBe Location(worldMock, -15.0, 15.0, -15.0) + } + + @Test + fun `should return the center of the area with decimal value`() { + val min = Location(worldMock, 10.6, 10.8, 10.4) + val max = Location(worldMock, 10.0, 10.0, 20.0) + val area = CubeArea(min, max) + area.location shouldBe Location(worldMock, 10.3, 10.4, 15.2) + } + } } From 6ff0c82928ff15d051a4e6eebe06c0cc47f03a9d Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 19 Aug 2023 18:44:12 +0200 Subject: [PATCH 141/143] chore: Import format --- .../com/github/rushyverse/api/serializer/EnumSerializer.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/serializer/EnumSerializer.kt b/src/main/kotlin/com/github/rushyverse/api/serializer/EnumSerializer.kt index 7b5ff34f..640d17cb 100644 --- a/src/main/kotlin/com/github/rushyverse/api/serializer/EnumSerializer.kt +++ b/src/main/kotlin/com/github/rushyverse/api/serializer/EnumSerializer.kt @@ -7,7 +7,6 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -import org.yaml.snakeyaml.serializer.SerializerException import kotlin.enums.EnumEntries /** From 5b0a6a5e1ea426c09302063b78560803e4da288f Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 19 Aug 2023 19:01:27 +0200 Subject: [PATCH 142/143] chore: Remove unnecessary function --- .../api/extension/_CoroutineScope.kt | 28 ------------------- .../api/extension/CoroutineScopeExt.kt | 11 -------- 2 files changed, 39 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt index 487b5772..37d2f27a 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt @@ -8,34 +8,6 @@ import kotlin.coroutines.CoroutineContext import kotlin.coroutines.coroutineContext import kotlin.time.Duration -/** - * Calls the specified suspending block with a given coroutine context into the [CoroutineScope], - * suspends until it completes, and returns the result. - * - * The resulting context for the [block] is derived by merging the current [coroutineContext] with the - * specified context using `coroutineContext + context` (see [CoroutineContext.plus]). - * This suspending function is cancellable. It immediately checks for cancellation of - * the resulting context and throws [CancellationException] if it is not [active][CoroutineContext.isActive]. - * - * This function uses dispatcher from the new context, shifting execution of the [block] into the - * different thread if a new dispatcher is specified, and back to the original dispatcher - * when it completes. Note that the result of `withContext` invocation is - * dispatched into the original context in a cancellable way with a **prompt cancellation guarantee**, - * which means that if the original [coroutineContext], in which `withContext` was invoked, - * is canceled by the time its dispatcher starts to execute the code, - * it discards the result of `withContext` and throws [CancellationException]. - * - * The cancellation behavior described above is enabled if and only if the dispatcher is being changed. - * For example, when using `withContext(NonCancellable) { ... }` there is no change in dispatcher and - * this call will not be canceled neither on entry to the block inside `withContext` nor on exit from it. - */ -public suspend inline fun withScopeContext( - scope: CoroutineScope, - noinline block: suspend CoroutineScope.() -> T -): T { - return withContext(scope.coroutineContext, block) -} - /** * Create a new scheduler with a task and run it. * The [CoroutineScope] for the scheduler is a children of the [CoroutineScope] receiver. diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/CoroutineScopeExt.kt b/src/test/kotlin/com/github/rushyverse/api/extension/CoroutineScopeExt.kt index 115078f1..f86ed772 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/CoroutineScopeExt.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/CoroutineScopeExt.kt @@ -10,17 +10,6 @@ import kotlin.time.Duration.Companion.seconds class CoroutineScopeExt { - @Test - fun `with scope context will use the coroutine context`() = runBlocking { - val scope = CoroutineScope(Dispatchers.Default) - val job = withScopeContext(scope) { - val currentJob = this.coroutineContext.job - assertTrue { currentJob in scope.coroutineContext.job.children } - currentJob - } - assertTrue { job !in scope.coroutineContext.job.children } - } - @Test fun `create running scheduler with task`() { val body: suspend SchedulerTask.Task.() -> Unit = {} From b12eef6ae3fb31e9ab9ff95809cc3f8855d7649f Mon Sep 17 00:00:00 2001 From: Distractic Date: Sat, 19 Aug 2023 19:04:10 +0200 Subject: [PATCH 143/143] chore: Import format --- .../com/github/rushyverse/api/extension/_CoroutineScope.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt b/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt index 37d2f27a..d1d700f5 100644 --- a/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt +++ b/src/main/kotlin/com/github/rushyverse/api/extension/_CoroutineScope.kt @@ -1,11 +1,7 @@ package com.github.rushyverse.api.extension import com.github.rushyverse.api.schedule.SchedulerTask -import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.withContext -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.coroutineContext import kotlin.time.Duration /**