diff --git a/build.gradle.kts b/build.gradle.kts index 87852fb..1930703 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,5 @@ import net.minecrell.pluginyml.bukkit.BukkitPluginDescription.Permission.Default +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id("java") @@ -25,8 +26,9 @@ repositories { maven("https://repo.incendo.org/content/repositories/snapshots") } -val cloudVersion = "1.6.2" +val cloudVersion = "1.7.1" val exposedVersion = "0.38.2" +val ktorVersion = "2.1.0" dependencies { compileOnly("io.papermc.paper", "paper-api", "1.19-R0.1-SNAPSHOT") @@ -43,21 +45,33 @@ dependencies { implementation("com.github.stefvanschie.inventoryframework", "IF", "0.10.6") + implementation("mysql", "mysql-connector-java", "8.0.30") + + implementation("io.ktor", "ktor-server-core", ktorVersion) + implementation("io.ktor", "ktor-server-netty", ktorVersion) + implementation("io.ktor", "ktor-server-content-negotiation", ktorVersion) + implementation("io.ktor", "ktor-serialization-kotlinx-json", ktorVersion) + implementation("io.ktor", "ktor-server-auth", ktorVersion) + implementation("io.ktor", "ktor-server-auth-jwt", ktorVersion) + implementation("io.ktor", "ktor-network-tls-certificates", ktorVersion) + + implementation("ch.qos.logback", "logback-classic", "1.2.11") + implementation("org.jetbrains.exposed", "exposed-core", exposedVersion) implementation("org.jetbrains.exposed", "exposed-dao", exposedVersion) implementation("org.jetbrains.exposed", "exposed-jdbc", exposedVersion) implementation("org.jetbrains.exposed", "exposed-java-time", exposedVersion) - implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") + implementation("org.jetbrains.kotlinx", "kotlinx-datetime", "0.4.0") - implementation("org.jetbrains.kotlinx", "kotlinx-coroutines-core", "1.6.2") - - implementation("com.github.shynixn.mccoroutine", "mccoroutine-bukkit-api", "2.2.0") - implementation("com.github.shynixn.mccoroutine", "mccoroutine-bukkit-core", "2.2.0") + implementation("org.jetbrains.kotlinx", "kotlinx-coroutines-core", "1.6.4") implementation("org.jetbrains.kotlinx", "kotlinx-serialization-json", "1.3.3") implementation("org.jetbrains.kotlinx", "kotlinx-serialization-hocon", "1.3.3") + implementation("com.github.shynixn.mccoroutine", "mccoroutine-bukkit-api", "2.7.0") + implementation("com.github.shynixn.mccoroutine", "mccoroutine-bukkit-core", "2.7.0") + implementation("com.google.api-client", "google-api-client", "1.35.1") implementation("com.google.oauth-client", "google-oauth-client-jetty", "1.34.1") implementation("com.google.apis", "google-api-services-sheets", "v4-rev20220606-1.32.1") @@ -65,6 +79,9 @@ dependencies { implementation("com.squareup.okhttp3", "okhttp", "4.10.0") bukkitLibrary("com.google.code.gson", "gson", "2.8.7") + + compileOnly("xyz.jpenilla", "squaremap-api", "1.1.8") + implementation(kotlin("stdlib-jdk8")) } java { @@ -102,45 +119,68 @@ bukkit { apiVersion = "1.19" depend = listOf("Vault") - libraries = listOf("com.github.shynixn.mccoroutine:mccoroutine-bukkit-api:2.2.0", "com.github.shynixn.mccoroutine:mccoroutine-bukkit-core:2.2.0") + libraries = listOf("com.github.shynixn.mccoroutine:mccoroutine-bukkit-api:2.7.0", "com.github.shynixn.mccoroutine:mccoroutine-bukkit-core:2.7.0") permissions { register("RaceAssist.admin") { default = Default.OP - children = listOf("raceassist.commands.audience.join", - "raceassist.commands.audience.leave", - "raceassist.commands.audience.list", + children = listOf("raceassist.commands.audience.leave", + "raceassist.commands.place.degree", + "raceassist.commands.bet.sheet", + "raceassist.commands.race.start", + "raceassist.commands.player.add", + "raceassist.commands.horse.ownerdelete", "raceassist.commands.bet.can", - "raceassist.commands.bet.delete", + "raceassist.commands.setting.copy", + "raceassist.commands.player.remove", "raceassist.commands.bet.list", + "raceassist.commands.bet.delete", + "raceassist.commands.setting.view", + "raceassist.commands.place.central", + "raceassist.commands.audience.list", + "raceassist.commands.setting.staff", + "raceassist.commands.setting.create", "raceassist.commands.bet.open", - "raceassist.commands.bet.rate", - "raceassist.commands.bet.revert", - "raceassist.commands.bet.return", - "raceassist.commands.bet.remove", - "raceassist.commands.bet.sheet", "raceassist.commands.place.reverse", - "raceassist.commands.place.central", - "raceassist.commands.place.degree", - "raceassist.commands.place.lap", + "raceassist.commands.bet.revert.jockey", + "raceassist.commands.reload", + "raceassist.commands.web", "raceassist.commands.place.set", - "raceassist.commands.place.finish", - "raceassist.commands.player.add", - "raceassist.commands.player.remove", "raceassist.commands.player.delete", "raceassist.commands.player.list", - "raceassist.commands.race.start", "raceassist.commands.race.debug", - "raceassist.commands.race.stop", - "raceassist.commands.setting.create", + "raceassist.commands.race.horse", + "raceassist.commands.bet.revert.row", + "raceassist.commands.place.finish", + "raceassist.commands.bet.return.jockey", "raceassist.commands.setting.delete", - "raceassist.commands.setting.copy", - "raceassist.commands.setting.staff") + "raceassist.commands.race.stop", + "raceassist.commands.bet.unit", + "raceassist.commands.bet.revert.all", + "raceassist.commands.place.lap", + "raceassist.commands.bet.rate", + "raceassist.commands.player.replacement", + "raceassist.commands.audience.join", + "raceassist.command.help") } register("RaceAssist.user") { default = Default.TRUE - children = listOf("raceassist.commands.audience.join", "raceassist.commands.audience.leave", "raceassist.commands.bet.open") + children = listOf( + "raceassist.commands.audience.join", + "raceassist.commands.audience.leave", + "raceassist.commands.bet.open", + "raceassist.commands.web", + "raceassist.commands.horse.ownerdelete", + ) } } +} +val compileKotlin: KotlinCompile by tasks +compileKotlin.kotlinOptions { + jvmTarget = "1.8" +} +val compileTestKotlin: KotlinCompile by tasks +compileTestKotlin.kotlinOptions { + jvmTarget = "1.8" } \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/RaceAssist.kt b/src/main/kotlin/dev/nikomaru/raceassist/RaceAssist.kt index bbed6a6..8c94341 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/RaceAssist.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/RaceAssist.kt @@ -22,15 +22,16 @@ import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator import cloud.commandframework.kotlin.coroutines.annotations.installCoroutineSupport import cloud.commandframework.meta.SimpleCommandMeta import cloud.commandframework.paper.PaperCommandManager -import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin -import com.github.shynixn.mccoroutine.bukkit.registerSuspendingEvents +import com.github.shynixn.mccoroutine.bukkit.* import dev.nikomaru.raceassist.api.VaultAPI import dev.nikomaru.raceassist.bet.commands.* import dev.nikomaru.raceassist.bet.event.BetGuiClickEvent import dev.nikomaru.raceassist.data.database.BetList +import dev.nikomaru.raceassist.data.database.UserAuthData import dev.nikomaru.raceassist.files.Config import dev.nikomaru.raceassist.horse.commands.OwnerDeleteCommand import dev.nikomaru.raceassist.horse.events.HorseBreedEvent +import dev.nikomaru.raceassist.horse.events.HorseKillEvent import dev.nikomaru.raceassist.race.commands.HelpCommand import dev.nikomaru.raceassist.race.commands.ReloadCommand import dev.nikomaru.raceassist.race.commands.audience.* @@ -39,15 +40,22 @@ import dev.nikomaru.raceassist.race.commands.player.* import dev.nikomaru.raceassist.race.commands.race.* import dev.nikomaru.raceassist.race.commands.setting.* import dev.nikomaru.raceassist.race.event.* -import dev.nikomaru.raceassist.utils.CommandSuggestions -import dev.nikomaru.raceassist.utils.Lang +import dev.nikomaru.raceassist.utils.* +import dev.nikomaru.raceassist.utils.coroutines.async import dev.nikomaru.raceassist.utils.coroutines.minecraft -import kotlinx.coroutines.withContext +import dev.nikomaru.raceassist.web.WebCommand +import dev.nikomaru.raceassist.web.api.WebAPI +import kotlinx.coroutines.* import kotlinx.serialization.ExperimentalSerializationApi +import org.apache.commons.lang.StringUtils import org.bukkit.command.CommandSender -import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.transactions.transaction import java.io.File +import java.io.InputStreamReader +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.util.* class RaceAssist : SuspendingJavaPlugin() { @@ -59,17 +67,55 @@ class RaceAssist : SuspendingJavaPlugin() { Config.load() settingDatabase() setCommand() + loadResources() registerEvents() - withContext(minecraft) { + withContext(Dispatchers.minecraft) { VaultAPI.setupEconomy() } + settingWebAPI() + } + + private fun settingWebAPI() { + if (Config.config.webAPI != null) { + if (Config.config.mySQL == null) { + plugin.logger.warning("MySQLが設定されていないため、WebAPIを起動できません。") + return + } + launch { + async(Dispatchers.async) { + WebAPI.startServer() + } + } + } + } + + private suspend fun loadResources() { + withContext(Dispatchers.IO) { + val conf = Properties() + conf.load(InputStreamReader(this.javaClass.classLoader.getResourceAsStream("MapColorDefault.properties")!!, "UTF-8")) + Utils.mapColor = conf + } } private fun settingDatabase() { - org.jetbrains.exposed.sql.Database.connect(url = "jdbc:sqlite:${plugin.dataFolder}${File.separator}RaceAssist.db", driver = "org.sqlite.JDBC") - transaction { - SchemaUtils.create(BetList) + if (Config.config.mySQL != null) { + Class.forName("com.mysql.cj.jdbc.Driver") + Database.connect(url = "jdbc:mysql://${Config.config.mySQL!!.url}", + driver = "com.mysql.cj.jdbc.Driver", + user = Config.config.mySQL!!.username, + password = Config.config.mySQL!!.password) + + transaction { + SchemaUtils.create(BetList, UserAuthData) + } + } else { + Database.connect(url = "jdbc:sqlite:${plugin.dataFolder}${File.separator}RaceAssist.db", driver = "org.sqlite.JDBC") + + transaction { + SchemaUtils.create(BetList) + } } + } override fun onDisable() { @@ -84,7 +130,7 @@ class RaceAssist : SuspendingJavaPlugin() { java.util.function.Function.identity()) - if (commandManager.queryCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION)) { + if (commandManager.hasCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION)) { commandManager.registerAsynchronousCompletions() } @@ -100,11 +146,12 @@ class RaceAssist : SuspendingJavaPlugin() { parse(AudienceListCommand()) parse(PlaceCentralCommand()) + parse(PlaceCreateCommand()) parse(PlaceDegreeCommand()) parse(PlaceFinishCommand()) - parse(PlaceLapCommand()) parse(PlaceReverseCommand()) parse(PlaceSetCommand()) + parse(PlaceStaffCommand()) parse(PlayerAddCommand()) parse(PlayerDeleteCommand()) @@ -115,6 +162,7 @@ class RaceAssist : SuspendingJavaPlugin() { parse(RaceStartCommand()) parse(RaceStopCommand()) parse(RaceDebugCommand()) + parse(RaceHorseCommand()) parse(BetCanCommand()) parse(BetDeleteCommand()) @@ -123,12 +171,15 @@ class RaceAssist : SuspendingJavaPlugin() { parse(BetRateCommand()) parse(BetRevertCommand()) parse(BetSheetCommand()) - parse(BetReturnCommand()) + parse(BetPayCommand()) parse(BetUnitCommand()) + parse(SettingCopyCommand()) parse(SettingCreateCommand()) parse(SettingDeleteCommand()) - parse(SettingCopyCommand()) + parse(SettingLapCommand()) + parse(SettingPlaceIdCommand()) + parse(SettingReplacemcntCommand()) parse(SettingStaffCommand()) parse(SettingViewCommand()) @@ -136,6 +187,36 @@ class RaceAssist : SuspendingJavaPlugin() { parse(ReloadCommand()) parse(OwnerDeleteCommand()) + + } + if (Config.config.webAPI != null) { + with(annotationParser) { + parse(WebCommand()) + } + } + + val debug = false + if (debug) { + val commandFile = File("D:\\Desktop\\commands.txt") + val commandList = mutableListOf() + val permissionList = hashSetOf() + commandManager.commands.stream().forEach { command -> + var string = "" + command.arguments.forEach { + val argment = when (it::class.java.simpleName) { + "CommandArgument" -> "<${it.name}>" + else -> it.name + } + string += "$argment " + } + string = StringUtils.removeEnd(string, " ") + commandList.add("` $string `
") + permissionList.add("\"${command.commandPermission}\"") + commandList.add("permission: ` ${command.commandPermission} `
") + commandList.add("") + } + println(permissionList) + Files.write(commandFile.toPath(), commandList, StandardCharsets.UTF_8) } } @@ -145,6 +226,7 @@ class RaceAssist : SuspendingJavaPlugin() { server.pluginManager.registerSuspendingEvents(SetCentralPointEvent(), this) server.pluginManager.registerSuspendingEvents(BetGuiClickEvent(), this) server.pluginManager.registerSuspendingEvents(HorseBreedEvent(), this) + server.pluginManager.registerSuspendingEvents(HorseKillEvent(), this) } companion object { diff --git a/src/main/kotlin/dev/nikomaru/raceassist/bet/BetUtils.kt b/src/main/kotlin/dev/nikomaru/raceassist/bet/BetUtils.kt index ec9100c..f45c276 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/bet/BetUtils.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/bet/BetUtils.kt @@ -104,18 +104,15 @@ object BetUtils { } //払い戻し - suspend fun returnBet(jockey: OfflinePlayer, raceId: String, sender: CommandSender, locale: Locale) { + suspend fun payDividend(jockey: OfflinePlayer, raceId: String, sender: CommandSender, locale: Locale) { val odds = getOdds(raceId, jockey) val eco = VaultAPI.getEconomy() newSuspendedTransaction(Dispatchers.IO) { BetList.select { (BetList.jockey eq jockey.name.toString()) and (BetList.raceId eq raceId) }.forEach { - val returnAmount = it[BetList.betting] * odds val retunrPlayer = Bukkit.getOfflinePlayer(it[BetList.playerUUID].toUUID()) - - withContext(minecraft) { - + withContext(Dispatchers.minecraft) { eco.depositPlayer(retunrPlayer, returnAmount) eco.withdrawPlayer(RaceSettingData.getOwner(raceId), returnAmount) } diff --git a/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetCanCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetCanCommand.kt index d77521c..493ab4c 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetCanCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetCanCommand.kt @@ -19,20 +19,20 @@ package dev.nikomaru.raceassist.bet.commands import cloud.commandframework.annotations.* import dev.nikomaru.raceassist.data.files.BetSettingData +import dev.nikomaru.raceassist.data.files.RaceUtils import dev.nikomaru.raceassist.utils.Lang -import dev.nikomaru.raceassist.utils.Utils import dev.nikomaru.raceassist.utils.Utils.locale import org.bukkit.command.CommandSender @CommandMethod("ra|RaceAssist bet") class BetCanCommand { @CommandPermission("raceassist.commands.bet.can") - @CommandMethod("can ") + @CommandMethod("can ") @CommandDescription("そのレースに対しての賭けることが可能か設定します") suspend fun setCanBet(sender: CommandSender, - @Argument(value = "raceId", suggestions = "raceId") raceId: String, + @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String, @Argument(value = "type", suggestions = "betType") type: String) { - if (Utils.returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return if (type == "on") { setCanBet(raceId, sender) } else if (type == "off") { diff --git a/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetDeleteCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetDeleteCommand.kt index 62a1249..2439e55 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetDeleteCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetDeleteCommand.kt @@ -19,23 +19,23 @@ package dev.nikomaru.raceassist.bet.commands import cloud.commandframework.annotations.* import dev.nikomaru.raceassist.bet.BetUtils +import dev.nikomaru.raceassist.data.files.RaceUtils import dev.nikomaru.raceassist.utils.Lang -import dev.nikomaru.raceassist.utils.Utils import org.bukkit.command.CommandSender import org.bukkit.entity.Player @CommandMethod("ra|RaceAssist bet") class BetDeleteCommand { @CommandPermission("raceassist.commands.bet.delete") - @CommandMethod("delete ") + @CommandMethod("delete ") @Confirmation @CommandDescription("レースに対して駆けられているものを削除します") - suspend fun delete(sender: CommandSender, @Argument(value = "raceId", suggestions = "raceId") raceId: String) { + suspend fun delete(sender: CommandSender, @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String) { if (sender !is Player) { sender.sendMessage("Only the player can do this.") return } - if (Utils.returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return BetUtils.deleteBetData(raceId) sender.sendMessage(Lang.getComponent("bet-remove-race", sender.locale(), raceId)) diff --git a/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetListCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetListCommand.kt index d481f82..15e11ab 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetListCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetListCommand.kt @@ -19,8 +19,8 @@ package dev.nikomaru.raceassist.bet.commands import cloud.commandframework.annotations.* import dev.nikomaru.raceassist.bet.BetUtils +import dev.nikomaru.raceassist.data.files.RaceUtils import dev.nikomaru.raceassist.utils.Lang -import dev.nikomaru.raceassist.utils.Utils import dev.nikomaru.raceassist.utils.Utils.locale import org.bukkit.Bukkit import org.bukkit.command.CommandSender @@ -28,11 +28,11 @@ import org.bukkit.command.CommandSender @CommandMethod("ra|RaceAssist bet") class BetListCommand { @CommandPermission("raceassist.commands.bet.list") - @CommandMethod("list ") + @CommandMethod("list ") @CommandDescription("現在賭けられている一覧を表示します") - suspend fun list(sender: CommandSender, @Argument(value = "raceId", suggestions = "raceId") raceId: String) { + suspend fun list(sender: CommandSender, @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String) { val locale = sender.locale() - if (Utils.returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return val list = BetUtils.listBetData(raceId) if (list.isEmpty()) { sender.sendMessage(Lang.getComponent("no-one-betting", locale)) diff --git a/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetOpenCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetOpenCommand.kt index df15c73..a5a3648 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetOpenCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetOpenCommand.kt @@ -24,6 +24,7 @@ import dev.nikomaru.raceassist.data.files.BetSettingData import dev.nikomaru.raceassist.data.files.RaceSettingData import dev.nikomaru.raceassist.utils.Lang import dev.nikomaru.raceassist.utils.coroutines.minecraft +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.bukkit.command.CommandSender import org.bukkit.entity.Player @@ -47,7 +48,7 @@ class BetOpenCommand { return } BetUtils.removeTempBetData(sender) - withContext(minecraft) { + withContext(Dispatchers.minecraft) { sender.openInventory(BetChestGui().getGUI(sender, raceId)) } BetUtils.initializeTempBetData(raceId, sender) diff --git a/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetReturnCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetPayCommand.kt similarity index 89% rename from src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetReturnCommand.kt rename to src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetPayCommand.kt index f3c624f..ccdd9b5 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetReturnCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetPayCommand.kt @@ -26,14 +26,14 @@ import org.bukkit.Bukkit import org.bukkit.command.CommandSender @CommandMethod("ra|RaceAssist bet") -class BetReturnCommand { +class BetPayCommand { @CommandPermission("raceassist.commands.bet.return.jockey") - @CommandMethod("return ") + @CommandMethod("pay ") @CommandDescription("払い戻し用のコマンド") @Confirmation suspend fun returnJockey(sender: CommandSender, - @Argument(value = "raceId", suggestions = "raceId") raceId: String, + @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String, @Argument(value = "playerName", suggestions = "playerName") playerName: String) { val locale = sender.locale() val jockey = Bukkit.getOfflinePlayerIfCached(playerName) ?: return sender.sendMessage(Lang.getComponent("player-add-not-exist", locale)) @@ -42,7 +42,7 @@ class BetReturnCommand { return } if (!BetUtils.playerCanPay(raceId, BetUtils.getBetSum(raceId), sender)) return - BetUtils.returnBet(jockey, raceId, sender, locale) + BetUtils.payDividend(jockey, raceId, sender, locale) sender.sendMessage(Lang.getComponent("finish-pay", locale)) } } \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetRateCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetRateCommand.kt index 50d62ee..871c54d 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetRateCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetRateCommand.kt @@ -20,21 +20,21 @@ package dev.nikomaru.raceassist.bet.commands import cloud.commandframework.annotations.* import cloud.commandframework.annotations.specifier.Range import dev.nikomaru.raceassist.data.files.BetSettingData +import dev.nikomaru.raceassist.data.files.RaceUtils.hasRaceControlPermission import dev.nikomaru.raceassist.utils.Lang import dev.nikomaru.raceassist.utils.Utils.locale -import dev.nikomaru.raceassist.utils.Utils.returnCanRaceSetting import org.bukkit.command.CommandSender @CommandMethod("ra|RaceAssist bet") class BetRateCommand { @CommandPermission("raceassist.commands.bet.rate") - @CommandMethod("rate ") + @CommandMethod("rate ") @CommandDescription("レースの賭けのレートを設定します") suspend fun setRate(sender: CommandSender, - @Argument(value = "raceId", suggestions = "raceId") raceId: String, + @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String, @Argument(value = "rate") @Range(min = "0", max = "100") rate: Int) { val locale = sender.locale() - if (returnCanRaceSetting(raceId, sender)) return + if (!hasRaceControlPermission(raceId, sender)) return BetSettingData.setReturnPercent(raceId, rate) sender.sendMessage(Lang.getComponent("change-bet-rate-message", locale, raceId, rate)) } diff --git a/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetRevertCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetRevertCommand.kt index 694d41c..dee8c50 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetRevertCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetRevertCommand.kt @@ -22,8 +22,8 @@ import dev.nikomaru.raceassist.api.VaultAPI import dev.nikomaru.raceassist.bet.BetUtils import dev.nikomaru.raceassist.data.database.BetList import dev.nikomaru.raceassist.data.files.RaceSettingData +import dev.nikomaru.raceassist.data.files.RaceUtils import dev.nikomaru.raceassist.utils.Lang -import dev.nikomaru.raceassist.utils.Utils import dev.nikomaru.raceassist.utils.Utils.locale import dev.nikomaru.raceassist.utils.Utils.toUUID import dev.nikomaru.raceassist.utils.coroutines.minecraft @@ -40,26 +40,26 @@ import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransacti class BetRevertCommand { @CommandPermission("raceassist.commands.bet.revert.row") - @CommandMethod("row ") + @CommandMethod("row ") @CommandDescription("そのレースの指定した番号の行のベットを返金します") @Confirmation suspend fun returnRow(sender: CommandSender, - @Argument(value = "raceId", suggestions = "raceId") raceId: String, + @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String, @Argument(value = "row") row: Int) { - if (Utils.returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return if (!BetUtils.playerCanPay(raceId, BetUtils.getRowBet(raceId, row), sender)) return returnRowBet(row, raceId, sender) sender.sendMessage(Lang.getComponent("bet-revert-complete-message", sender.locale())) } @CommandPermission("raceassist.commands.bet.revert.jockey") - @CommandMethod("player ") + @CommandMethod("player ") @CommandDescription("そのレースに対して特定のプレイヤーのものを返金します") @Confirmation suspend fun returnPlayer(sender: CommandSender, - @Argument(value = "raceId", suggestions = "raceId") raceId: String, + @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String, @Argument(value = "playerName", suggestions = "playerName") playerName: String) { - if (Utils.returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return val jockey = Bukkit.getOfflinePlayerIfCached(playerName) ?: return sender.sendMessage(Lang.getComponent("player-add-not-exist", sender.locale())) if (!RaceSettingData.getJockeys(raceId).contains(jockey)) { @@ -72,11 +72,11 @@ class BetRevertCommand { } @CommandPermission("raceassist.commands.bet.revert.all") - @CommandMethod("all ") + @CommandMethod("all ") @CommandDescription("そのレースに対して賭けられたものをすべて返金します") @Confirmation - suspend fun returnAll(sender: CommandSender, @Argument(value = "raceId", suggestions = "raceId") raceId: String) { - if (Utils.returnCanRaceSetting(raceId, sender)) return + suspend fun returnAll(sender: CommandSender, @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String) { + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return if (!BetUtils.playerCanPay(raceId, BetUtils.getBetSum(raceId), sender)) return revertAllBet(raceId, sender) sender.sendMessage(Lang.getComponent("bet-revert-complete-message", sender.locale())) @@ -122,7 +122,7 @@ class BetRevertCommand { } } - private suspend fun transferMoney(sender: OfflinePlayer, receiver: OfflinePlayer, amount: Double) = withContext(minecraft) { + private suspend fun transferMoney(sender: OfflinePlayer, receiver: OfflinePlayer, amount: Double) = withContext(Dispatchers.minecraft) { val eco = VaultAPI.getEconomy() eco.withdrawPlayer(sender, amount) eco.depositPlayer(receiver, amount) diff --git a/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetSheetCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetSheetCommand.kt index d5fabf0..b260be8 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetSheetCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetSheetCommand.kt @@ -21,7 +21,7 @@ import cloud.commandframework.annotations.* import com.google.api.services.sheets.v4.model.* import dev.nikomaru.raceassist.api.sheet.SheetsServiceUtil.getSheetsService import dev.nikomaru.raceassist.data.files.BetSettingData -import dev.nikomaru.raceassist.utils.Utils.returnCanRaceSetting +import dev.nikomaru.raceassist.data.files.RaceUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.bukkit.command.CommandSender @@ -29,14 +29,12 @@ import org.bukkit.command.CommandSender @CommandMethod("ra|RaceAssist bet") class BetSheetCommand { @CommandPermission("raceassist.commands.bet.sheet") - @CommandMethod("sheet ") + @CommandMethod("sheet ") @CommandDescription("現在の賭け状況を閲覧できるシートを設定します") suspend fun sheet(sender: CommandSender, - @Argument(value = "raceId", suggestions = "raceId") raceId: String, + @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String, @Argument(value = "sheet") sheetId: String) { - withContext(Dispatchers.IO) { - if (returnCanRaceSetting(raceId, sender)) return@withContext - } + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return BetSettingData.setSpreadSheetId(raceId, sheetId) createNewSheets(sheetId, raceId) } diff --git a/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetUnitCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetUnitCommand.kt index b6fa557..0d663a6 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetUnitCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/bet/commands/BetUnitCommand.kt @@ -20,8 +20,8 @@ package dev.nikomaru.raceassist.bet.commands import cloud.commandframework.annotations.* import cloud.commandframework.annotations.specifier.Range import dev.nikomaru.raceassist.data.files.BetSettingData +import dev.nikomaru.raceassist.data.files.RaceUtils import dev.nikomaru.raceassist.utils.Lang -import dev.nikomaru.raceassist.utils.Utils import dev.nikomaru.raceassist.utils.Utils.locale import org.bukkit.command.CommandSender @@ -29,13 +29,13 @@ import org.bukkit.command.CommandSender class BetUnitCommand { @CommandPermission("raceassist.commands.bet.unit") - @CommandMethod("unit ") + @CommandMethod("unit ") @CommandDescription("最小の賭け単位を設定します") suspend fun setUnit(sender: CommandSender, - @Argument(value = "raceId", suggestions = "raceId") raceId: String, + @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String, @Argument(value = "unit") @Range(min = "1", max = "100000") unit: Int) { val locale = sender.locale() - if (Utils.returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return BetSettingData.setBetUnit(raceId, unit) sender.sendMessage(Lang.getComponent("change-bet-unit-message", locale, raceId, unit)) } diff --git a/src/main/kotlin/dev/nikomaru/raceassist/bet/data/TempBetData.kt b/src/main/kotlin/dev/nikomaru/raceassist/bet/data/TempBetData.kt index 2f2a477..96acc17 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/bet/data/TempBetData.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/bet/data/TempBetData.kt @@ -19,4 +19,4 @@ package dev.nikomaru.raceassist.bet.data import org.bukkit.OfflinePlayer -data class TempBetData(val raceId: String, val player: OfflinePlayer, val jockey: OfflinePlayer, var bet: Int) \ No newline at end of file +data class TempBetData(val raceId: String, val player: OfflinePlayer, val jockey: OfflinePlayer, var betPerUnit: Int) \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/bet/event/BetGuiClickEvent.kt b/src/main/kotlin/dev/nikomaru/raceassist/bet/event/BetGuiClickEvent.kt index 696d285..209db7b 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/bet/event/BetGuiClickEvent.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/bet/event/BetGuiClickEvent.kt @@ -89,7 +89,7 @@ class BetGuiClickEvent : Listener { while (iterator.hasNext()) { val it = iterator.next() if (it.raceId == raceId && it.player == player && it.jockey == AllPlayers[raceId]?.get(slot)) { - it.bet = selectedNowBet + 10 + it.betPerUnit = selectedNowBet + 10 } } @@ -103,6 +103,7 @@ class BetGuiClickEvent : Listener { delay(50) clicked.remove(player.uniqueId) } + in 9..16 -> { // 1 ↑ if (slot > (limit + 9)) { @@ -115,7 +116,7 @@ class BetGuiClickEvent : Listener { while (iterator.hasNext()) { val it = iterator.next() if (it.raceId == raceId && it.player == player && it.jockey == AllPlayers[raceId]?.get(slot - 9)) { - it.bet = selectedNowBet + 1 + it.betPerUnit = selectedNowBet + 1 } } @@ -130,6 +131,7 @@ class BetGuiClickEvent : Listener { delay(50) clicked.remove(player.uniqueId) } + in 27..34 -> { //1 ↓ if (slot > (limit + 27)) { @@ -149,7 +151,7 @@ class BetGuiClickEvent : Listener { while (iterator.hasNext()) { val it = iterator.next() if (it.raceId == raceId && it.player == player && it.jockey == AllPlayers[raceId]?.get(slot - 27)) { - it.bet = selectedNowBet - 1 + it.betPerUnit = selectedNowBet - 1 } } @@ -163,6 +165,7 @@ class BetGuiClickEvent : Listener { delay(50) clicked.remove(player.uniqueId) } + in 36..43 -> { // 10 ↓ if (slot > (limit + 36)) { @@ -184,7 +187,7 @@ class BetGuiClickEvent : Listener { while (iterator.hasNext()) { val it = iterator.next() if (it.raceId == raceId && it.player == player && it.jockey == AllPlayers[raceId]?.get(slot - 36)) { - it.bet = selectedNowBet - 10 + it.betPerUnit = selectedNowBet - 10 } } @@ -198,6 +201,7 @@ class BetGuiClickEvent : Listener { delay(50) clicked.remove(player.uniqueId) } + 17 -> { //clear player.playSound(player.location, Sound.UI_BUTTON_CLICK, 1f, 1f) @@ -205,7 +209,7 @@ class BetGuiClickEvent : Listener { while (iterator.hasNext()) { val it = iterator.next() if (it.raceId == raceId && it.player == player) { - it.bet = 0 + it.betPerUnit = 0 } } @@ -216,6 +220,7 @@ class BetGuiClickEvent : Listener { item.itemMeta = itemMeta } } + 35 -> { //deny player.closeInventory() @@ -230,6 +235,7 @@ class BetGuiClickEvent : Listener { } } + 44 -> { //accept @@ -253,18 +259,18 @@ class BetGuiClickEvent : Listener { val iterator = BetUtils.TempBetDatas.iterator() while (iterator.hasNext()) { val temp = iterator.next() - if (temp.raceId == raceId && temp.player == player && temp.bet != 0) { + if (temp.raceId == raceId && temp.player == player && temp.betPerUnit != 0) { BetList.insert { bet -> bet[BetList.raceId] = raceId bet[playerName] = player.name bet[playerUUID] = player.uniqueId.toString() bet[jockey] = temp.jockey.name.toString() bet[jockeyUUID] = temp.jockey.uniqueId.toString() - bet[betting] = temp.bet * betUnit + bet[betting] = temp.betPerUnit * betUnit bet[timeStamp] = LocalDateTime.now() bet[rowNum] = row + 1 } - betProcess(player, row, temp.bet, temp.jockey, eco, owner, raceId) + betProcess(player, row, temp.betPerUnit, temp.jockey, eco, owner, raceId) row++ } } @@ -335,7 +341,7 @@ class BetGuiClickEvent : Listener { while (iterator.hasNext()) { val it = iterator.next() if (it.raceId == raceId && it.player == player && it.jockey == AllPlayers[raceId]?.get(slot)) { - bet = it.bet + bet = it.betPerUnit } } return bet @@ -345,7 +351,7 @@ class BetGuiClickEvent : Listener { var sum = 0 BetUtils.TempBetDatas.forEach { if (it.raceId == raceId && it.player == player) { - sum += it.bet + sum += it.betPerUnit } } return sum diff --git a/src/main/kotlin/dev/nikomaru/raceassist/data/database/UserAuthData.kt b/src/main/kotlin/dev/nikomaru/raceassist/data/database/UserAuthData.kt new file mode 100644 index 0000000..51033b8 --- /dev/null +++ b/src/main/kotlin/dev/nikomaru/raceassist/data/database/UserAuthData.kt @@ -0,0 +1,25 @@ +/* + * Copyright © 2021-2022 Nikomaru + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package dev.nikomaru.raceassist.data.database + +import org.jetbrains.exposed.sql.Table + +object UserAuthData : Table() { + val uuid = varchar("UUID", 40) + val hashedPassword = varchar("PASSWORD", 256) +} diff --git a/src/main/kotlin/dev/nikomaru/raceassist/data/files/BetSettingData.kt b/src/main/kotlin/dev/nikomaru/raceassist/data/files/BetSettingData.kt index 031c4d1..6e83ad4 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/data/files/BetSettingData.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/data/files/BetSettingData.kt @@ -41,26 +41,23 @@ object BetSettingData { } suspend fun setAvailable(raceId: String, available: Boolean) = withContext(Dispatchers.IO) { - val data = getRaceConfig(raceId) - data.bet.available = available - data.save(raceId) + val bet = getRaceConfig(raceId).bet.copy(available = available) + getRaceConfig(raceId).copy(bet = bet).save() } suspend fun setReturnPercent(raceId: String, returnPercent: Int) = withContext(Dispatchers.IO) { - val data = getRaceConfig(raceId) - data.bet.returnPercent = returnPercent - data.save(raceId) + val bet = getRaceConfig(raceId).bet.copy(returnPercent = returnPercent) + getRaceConfig(raceId).copy(bet = bet).save() } suspend fun setSpreadSheetId(raceId: String, spreadSheetId: String) = withContext(Dispatchers.IO) { - val data = getRaceConfig(raceId) - data.bet.spreadSheetId = spreadSheetId - data.save(raceId) + val bet = getRaceConfig(raceId).bet.copy(spreadSheetId = spreadSheetId) + getRaceConfig(raceId).copy(bet = bet).save() + } suspend fun setBetUnit(raceId: String, betUnit: Int) { - val data = getRaceConfig(raceId) - data.bet.betUnit = betUnit - data.save(raceId) + val bet = getRaceConfig(raceId).bet.copy(betUnit = betUnit) + getRaceConfig(raceId).copy(bet = bet).save() } } \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/data/files/PlaceSettingData.kt b/src/main/kotlin/dev/nikomaru/raceassist/data/files/PlaceSettingData.kt index 0f019d9..eefca6e 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/data/files/PlaceSettingData.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/data/files/PlaceSettingData.kt @@ -17,82 +17,131 @@ package dev.nikomaru.raceassist.data.files -import dev.nikomaru.raceassist.data.files.RaceUtils.getRaceConfig +import dev.nikomaru.raceassist.RaceAssist +import dev.nikomaru.raceassist.data.files.RaceUtils.getPlaceConfig import dev.nikomaru.raceassist.data.files.RaceUtils.save import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import kotlinx.serialization.json.encodeToJsonElement +import org.bukkit.OfflinePlayer import java.awt.Polygon +import java.io.* object PlaceSettingData { - suspend fun getLap(raceId: String) = withContext(Dispatchers.IO) { - getRaceConfig(raceId).place.lap + suspend fun existsPlace(placeId: String) = withContext(Dispatchers.IO) { + val file = RaceAssist.plugin.dataFolder.resolve("PlaceData").resolve("$placeId.json") + return@withContext file.exists() } - suspend fun getInsidePolygon(raceId: String) = withContext(Dispatchers.IO) { - getRaceConfig(raceId).place.inside + suspend fun createPlace(placeId: String, owner: OfflinePlayer): Boolean = withContext(Dispatchers.IO) { + val file = File(File(RaceAssist.plugin.dataFolder, "PlaceData"), "$placeId.json") + if (!file.parentFile.exists()) { + file.parentFile.mkdirs() + } + if (file.exists()) { + return@withContext false + } + + val placeConfig = PlaceConfig(placeId, null, null, 0, false, Polygon(), Polygon(), owner, arrayListOf(owner)) + val json = json.encodeToJsonElement(placeConfig) + val string = json.toString() + + file.createNewFile() + val fw = PrintWriter(BufferedWriter(OutputStreamWriter(FileOutputStream(file), "UTF-8"))) + fw.write(string) + fw.close() + + return@withContext true } - suspend fun getOutsidePolygon(raceId: String) = withContext(Dispatchers.IO) { - getRaceConfig(raceId).place.outside + suspend fun copyPlace(placeId_1: String, placeId_2: String, owner: OfflinePlayer) = withContext(Dispatchers.IO) { + val beforeData = RaceUtils.getPlaceConfig(placeId_1) + val afterData = beforeData.copy(placeId = placeId_2, owner = owner, staff = arrayListOf(owner)) + afterData.save() } - suspend fun getCentralXPoint(raceId: String) = withContext(Dispatchers.IO) { - getRaceConfig(raceId).place.centralX + fun deletePlace(placeId: String) { + val file = File(File(RaceAssist.plugin.dataFolder, "PlaceData"), "$placeId.json") + file.delete() } - suspend fun getCentralYPoint(raceId: String) = withContext(Dispatchers.IO) { - getRaceConfig(raceId).place.centralY + suspend fun getInsidePolygon(placeId: String) = withContext(Dispatchers.IO) { + getPlaceConfig(placeId).inside } - suspend fun getGoalDegree(raceId: String) = withContext(Dispatchers.IO) { - getRaceConfig(raceId).place.goalDegree + suspend fun getOutsidePolygon(placeId: String) = withContext(Dispatchers.IO) { + getPlaceConfig(placeId).outside } - suspend fun getReverse(raceId: String) = withContext(Dispatchers.IO) { - getRaceConfig(raceId).place.reverse + suspend fun getCentralXPoint(placeId: String) = withContext(Dispatchers.IO) { + getPlaceConfig(placeId).centralX } - suspend fun setLap(raceId: String, lap: Int) = withContext(Dispatchers.IO) { - val data = getRaceConfig(raceId) - data.place.lap = lap - data.save(raceId) + suspend fun getCentralYPoint(placeId: String) = withContext(Dispatchers.IO) { + getPlaceConfig(placeId).centralY } - suspend fun setInsidePolygon(raceId: String, polygon: Polygon) = withContext(Dispatchers.IO) { - val data = getRaceConfig(raceId) - data.place.inside = polygon - data.save(raceId) + suspend fun getGoalDegree(placeId: String) = withContext(Dispatchers.IO) { + getPlaceConfig(placeId).goalDegree } - suspend fun setOutsidePolygon(raceId: String, polygon: Polygon) = withContext(Dispatchers.IO) { - val data = getRaceConfig(raceId) - data.place.outside = polygon - data.save(raceId) + suspend fun getReverse(placeId: String) = withContext(Dispatchers.IO) { + getPlaceConfig(placeId).reverse } - suspend fun setCentralXPoint(raceId: String, x: Int) = withContext(Dispatchers.IO) { - val data = getRaceConfig(raceId) - data.place.centralX = x - data.save(raceId) + suspend fun setInsidePolygon(placeId: String, polygon: Polygon) = withContext(Dispatchers.IO) { + getPlaceConfig(placeId).copy(inside = polygon).save() } - suspend fun setCentralYPoint(raceId: String, y: Int) = withContext(Dispatchers.IO) { - val data = getRaceConfig(raceId) - data.place.centralY = y - data.save(raceId) + suspend fun setOutsidePolygon(placeId: String, polygon: Polygon) = withContext(Dispatchers.IO) { + getPlaceConfig(placeId).copy(outside = polygon).save() } - suspend fun setGoalDegree(raceId: String, degree: Int) = withContext(Dispatchers.IO) { - val data = getRaceConfig(raceId) - data.place.goalDegree = degree - data.save(raceId) + suspend fun setCentralXPoint(placeId: String, x: Int) = withContext(Dispatchers.IO) { + getPlaceConfig(placeId).copy(centralX = x).save() } - suspend fun setReverse(raceId: String, reverse: Boolean) = withContext(Dispatchers.IO) { - val data = getRaceConfig(raceId) - data.place.reverse = reverse - data.save(raceId) + suspend fun setCentralYPoint(placeId: String, y: Int) = withContext(Dispatchers.IO) { + getPlaceConfig(placeId).copy(centralY = y).save() + } + + suspend fun setGoalDegree(placeId: String, degree: Int) = withContext(Dispatchers.IO) { + getPlaceConfig(placeId).copy(goalDegree = degree).save() + } + + suspend fun setReverse(placeId: String, reverse: Boolean) = withContext(Dispatchers.IO) { + getPlaceConfig(placeId).copy(reverse = reverse).save() + } + + suspend fun getStaffs(placeId: String) = withContext(Dispatchers.IO) { + getPlaceConfig(placeId).staff + } + + suspend fun addStaff(placeId: String, player: OfflinePlayer) = withContext(Dispatchers.IO) { + if (existStaff(placeId, player)) return@withContext false + val data = getPlaceConfig(placeId) + data.staff.add(player) + data.save() + return@withContext true + } + + suspend fun existStaff(placeId: String, player: OfflinePlayer) = withContext(Dispatchers.IO) { + getPlaceConfig(placeId).staff.contains(player) + } + + suspend fun removeStaff(placeId: String, player: OfflinePlayer) = withContext(Dispatchers.IO) { + if (getOwner(placeId) == player || !existStaff(placeId, player)) { + return@withContext false //Owner can't be removed or staff can't be removed if they aren't in the list + } + val data = getPlaceConfig(placeId) + data.staff.remove(player) + data.save() + return@withContext true + } + + suspend fun getOwner(placeId: String) = withContext(Dispatchers.IO) { + getPlaceConfig(placeId).owner } } \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/data/files/RaceSettingData.kt b/src/main/kotlin/dev/nikomaru/raceassist/data/files/RaceSettingData.kt index 43072cd..055a4ab 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/data/files/RaceSettingData.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/data/files/RaceSettingData.kt @@ -26,18 +26,17 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.serialization.json.encodeToJsonElement import org.bukkit.OfflinePlayer -import java.awt.Polygon import java.io.* import java.util.* object RaceSettingData { - fun existsRace(raceId: String): Boolean { - val file = File(File(RaceAssist.plugin.dataFolder, "RaceData"), "$raceId.json") - return file.exists() + suspend fun existsRace(raceId: String) = withContext(Dispatchers.IO) { + val file = RaceAssist.plugin.dataFolder.resolve("RaceData").resolve("$raceId.json") + return@withContext file.exists() } - suspend fun createRace(raceId: String, owner: OfflinePlayer): Boolean = withContext(Dispatchers.IO) { + suspend fun createRace(raceId: String, placeId: String, owner: OfflinePlayer): Boolean = withContext(Dispatchers.IO) { val file = File(File(RaceAssist.plugin.dataFolder, "RaceData"), "$raceId.json") if (!file.parentFile.exists()) { file.parentFile.mkdirs() @@ -47,9 +46,8 @@ object RaceSettingData { } val raceName = raceId.split("-")[0] - val place = Place(1, null, null, 0, false, Polygon(), Polygon()) val bet = Bet(false, 75, null, 100) - val raceConfig = RaceConfig(raceId, raceName, owner, arrayListOf(owner), arrayListOf(), place, bet, hashMapOf()) + val raceConfig = RaceConfig(raceId, raceName, owner, arrayListOf(owner), arrayListOf(), 0, placeId, bet, hashMapOf(), hashMapOf()) val json = json.encodeToJsonElement(raceConfig) val string = json.toString() @@ -62,14 +60,21 @@ object RaceSettingData { } suspend fun copyRace(raceId_1: String, raceId_2: String, owner: OfflinePlayer) = withContext(Dispatchers.IO) { - val data = getRaceConfig(raceId_1) - data.raceId = raceId_2 - data.owner = owner - data.staff = arrayListOf(owner) - data.bet.available = false - data.jockeys = arrayListOf() - data.save(raceId_2) + val beforeData = getRaceConfig(raceId_1) + val afterBetData = beforeData.bet.copy(available = false) + val afterData = beforeData.copy(raceId = raceId_2, owner = owner, staff = arrayListOf(owner), bet = afterBetData, jockeys = arrayListOf()) + afterData.save() + } + + suspend fun getPlaceId(raceId: String) = withContext(Dispatchers.IO) { + val raceConfig = getRaceConfig(raceId) + return@withContext raceConfig.placeId + } + suspend fun setPlaceId(raceId: String, placeId: String) = withContext(Dispatchers.IO) { + val beforeData = getRaceConfig(raceId) + val afterData = beforeData.copy(placeId = placeId) + afterData.save() } fun deleteRace(raceId: String) { @@ -77,46 +82,103 @@ object RaceSettingData { file.delete() } - suspend fun getJockeys(raceId: String): ArrayList = withContext(Dispatchers.IO) { - getRaceConfig(raceId).jockeys - } - suspend fun getOwner(raceId: String): OfflinePlayer = withContext(Dispatchers.IO) { getRaceConfig(raceId).owner } - suspend fun getReplacement(raceId: String): HashMap = withContext(Dispatchers.IO) { - getRaceConfig(raceId).replacement + suspend fun getJockeys(raceId: String): ArrayList = withContext(Dispatchers.IO) { + getRaceConfig(raceId).jockeys } suspend fun addJockey(raceId: String, jockey: OfflinePlayer) = withContext(Dispatchers.IO) { val data = getRaceConfig(raceId) data.jockeys.add(jockey) - data.save(raceId) + data.save() } - suspend fun setReplacement(raceId: String, uuid: UUID, name: String) = withContext(Dispatchers.IO) { + suspend fun removeJockey(raceId: String, jockey: OfflinePlayer) = withContext(Dispatchers.IO) { val data = getRaceConfig(raceId) - data.replacement[uuid] = name - data.save(raceId) + data.jockeys.remove(jockey) + data.save() } - suspend fun removeJockey(raceId: String, jockey: OfflinePlayer) = withContext(Dispatchers.IO) { + suspend fun getReplacement(raceId: String): HashMap = withContext(Dispatchers.IO) { + getRaceConfig(raceId).replacement + } + + suspend fun setReplacement(raceId: String, uuid: UUID, name: String) = withContext(Dispatchers.IO) { val data = getRaceConfig(raceId) - data.jockeys.remove(jockey) - data.save(raceId) + data.replacement[uuid] = name + data.save() } suspend fun removeReplacement(raceId: String, uuid: UUID) = withContext(Dispatchers.IO) { val data = getRaceConfig(raceId) data.replacement.remove(uuid) - data.save(raceId) + data.save() } suspend fun deleteReplacement(raceId: String) = withContext(Dispatchers.IO) { val data = getRaceConfig(raceId) data.replacement.clear() - data.save(raceId) + data.save() + } + + suspend fun getHorse(raceId: String): HashMap = withContext(Dispatchers.IO) { + getRaceConfig(raceId).horse + } + + suspend fun setHorse(raceId: String, playerUniqueId: UUID, horseUniqueId: UUID) = withContext(Dispatchers.IO) { + val data = getRaceConfig(raceId) + data.horse[playerUniqueId] = horseUniqueId + data.save() + } + + suspend fun removeHorse(raceId: String, uuid: UUID) = withContext(Dispatchers.IO) { + val data = getRaceConfig(raceId) + data.horse.remove(uuid) + data.save() + } + + suspend fun deleteHorse(raceId: String) = withContext(Dispatchers.IO) { + val data = getRaceConfig(raceId) + data.horse.clear() + data.save() + } + + suspend fun getStaffs(raceId: String) = withContext(Dispatchers.IO) { + getRaceConfig(raceId).staff + } + + suspend fun addStaff(raceId: String, player: OfflinePlayer) = withContext(Dispatchers.IO) { + if (existStaff(raceId, player)) return@withContext false + val data = getRaceConfig(raceId) + data.staff.add(player) + data.save() + return@withContext true + } + + suspend fun removeStaff(raceId: String, player: OfflinePlayer) = withContext(Dispatchers.IO) { + if (getOwner(raceId) == player || !existStaff(raceId, player)) { + return@withContext false //Owner can't be removed or staff can't be removed if they aren't in the list + } + val data = getRaceConfig(raceId) + data.staff.remove(player) + data.save() + return@withContext true + } + + suspend fun existStaff(raceId: String, staff: OfflinePlayer) = withContext(Dispatchers.IO) { + return@withContext getRaceConfig(raceId).staff.contains(staff) + } + + suspend fun getLap(raceId: String) = withContext(Dispatchers.IO) { + return@withContext getRaceConfig(raceId).lap + } + + suspend fun setLap(raceId: String, lap: Int) = withContext(Dispatchers.IO) { + val data = getRaceConfig(raceId).copy(lap = lap) + data.save() } } \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/data/files/RaceUtils.kt b/src/main/kotlin/dev/nikomaru/raceassist/data/files/RaceUtils.kt index e4b84e1..0a4afaf 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/data/files/RaceUtils.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/data/files/RaceUtils.kt @@ -18,6 +18,8 @@ package dev.nikomaru.raceassist.data.files import dev.nikomaru.raceassist.RaceAssist.Companion.plugin +import dev.nikomaru.raceassist.data.files.RaceSettingData.existsRace +import dev.nikomaru.raceassist.utils.Lang import dev.nikomaru.raceassist.utils.Utils.toUUID import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -29,12 +31,16 @@ import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.* import org.bukkit.Bukkit import org.bukkit.OfflinePlayer +import org.bukkit.command.CommandSender +import org.bukkit.command.ConsoleCommandSender +import org.bukkit.entity.Player import java.awt.Polygon import java.util.* object RaceUtils { - suspend fun RaceConfig.save(raceId: String) { + suspend fun RaceConfig.save() { + val raceId = this.raceId val file = plugin.dataFolder.resolve("RaceData").resolve("$raceId.json") val json = json.encodeToJsonElement(this) val string = json.toString() @@ -44,34 +50,89 @@ object RaceUtils { } } + suspend fun PlaceConfig.save() { + val placeId = this.placeId + val file = plugin.dataFolder.resolve("PlaceData").resolve("$placeId.json") + val json = json.encodeToJsonElement(this) + val string = json.toString() + withContext(Dispatchers.IO) { + file.createNewFile() + file.writeText(string) + } + } + suspend fun getRaceConfig(raceId: String) = withContext(Dispatchers.IO) { val file = plugin.dataFolder.resolve("RaceData").resolve("$raceId.json") return@withContext json.decodeFromString(file.readText()) } + suspend fun getPlaceConfig(placeId: String) = withContext(Dispatchers.IO) { + val file = plugin.dataFolder.resolve("PlaceData").resolve("$placeId.json") + return@withContext json.decodeFromString(file.readText()) + } + + suspend fun hasPlaceControlPermission(placeId: String, player: CommandSender) = withContext(Dispatchers.IO) { + if (player is ConsoleCommandSender) { + return@withContext true + } + (player as Player) + if (!PlaceSettingData.existsPlace(placeId)) { + player.sendMessage(Lang.getComponent("no-exist-this-placeid-race", player.locale(), placeId)) + return@withContext false + } + if (!PlaceSettingData.existStaff(placeId, player)) { + player.sendMessage(Lang.getComponent("only-place-creator-can-setting", player.locale())) + return@withContext false + } + return@withContext true + } + + suspend fun hasRaceControlPermission(raceId: String, player: CommandSender) = withContext(Dispatchers.IO) { + if (player is ConsoleCommandSender) { + return@withContext true + } + (player as Player) + if (!existsRace(raceId)) { + player.sendMessage(Lang.getComponent("no-exist-this-raceid-race", player.locale(), raceId)) + return@withContext false + } + if (!RaceSettingData.existStaff(raceId, player)) { + player.sendMessage(Lang.getComponent("only-race-creator-can-setting", player.locale())) + return@withContext false + } + return@withContext true + } + } +//TODO 馬に関するデータを保存する @Serializable -data class RaceConfig(var raceId: String, - var raceName: String, - var owner: @Serializable(with = OfflinePlayerSerializer::class) OfflinePlayer, - var staff: ArrayList<@Serializable(with = OfflinePlayerSerializer::class) OfflinePlayer>, - var jockeys: ArrayList<@Serializable(with = OfflinePlayerSerializer::class) OfflinePlayer>, - val place: Place, +data class RaceConfig(val raceId: String, + val raceName: String, + val owner: @Serializable(with = OfflinePlayerSerializer::class) OfflinePlayer, + val staff: ArrayList<@Serializable(with = OfflinePlayerSerializer::class) OfflinePlayer>, + val jockeys: ArrayList<@Serializable(with = OfflinePlayerSerializer::class) OfflinePlayer>, + val lap: Int, + val placeId: String, val bet: Bet, - val replacement: HashMap<@Serializable(with = UUIDSerializer::class) UUID, String>) + val replacement: HashMap<@Serializable(with = UUIDSerializer::class) UUID, String>, + val horse: HashMap<@Serializable(with = UUIDSerializer::class) UUID, @Serializable(with = UUIDSerializer::class) UUID>) @Serializable -data class Place(var lap: Int, - var centralX: Int?, - var centralY: Int?, - var goalDegree: Int, - var reverse: Boolean, - var inside: @Serializable(with = PolygonSerializer::class) Polygon, - var outside: @Serializable(with = PolygonSerializer::class) Polygon) +data class PlaceConfig( + val placeId: String, + val centralX: Int?, + val centralY: Int?, + val goalDegree: Int, + val reverse: Boolean, + val inside: @Serializable(with = PolygonSerializer::class) Polygon, + val outside: @Serializable(with = PolygonSerializer::class) Polygon, + val owner: @Serializable(with = OfflinePlayerSerializer::class) OfflinePlayer, + val staff: ArrayList<@Serializable(with = OfflinePlayerSerializer::class) OfflinePlayer>, +) @Serializable -data class Bet(var available: Boolean, var returnPercent: Int, var spreadSheetId: String?, var betUnit: Int) +data class Bet(val available: Boolean, val returnPercent: Int, val spreadSheetId: String?, val betUnit: Int) // UUID <==> String object UUIDSerializer : KSerializer { diff --git a/src/main/kotlin/dev/nikomaru/raceassist/data/files/StaffSettingData.kt b/src/main/kotlin/dev/nikomaru/raceassist/data/files/StaffSettingData.kt deleted file mode 100644 index ed685a0..0000000 --- a/src/main/kotlin/dev/nikomaru/raceassist/data/files/StaffSettingData.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright © 2021-2022 Nikomaru - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package dev.nikomaru.raceassist.data.files - -import dev.nikomaru.raceassist.data.files.RaceUtils.getRaceConfig -import dev.nikomaru.raceassist.data.files.RaceUtils.save -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import org.bukkit.OfflinePlayer - -object StaffSettingData { - - suspend fun getStaffs(raceId: String) = withContext(Dispatchers.IO) { - getRaceConfig(raceId).staff - } - - suspend fun addStaff(raceId: String, player: OfflinePlayer) = withContext(Dispatchers.IO) { - if (existStaff(raceId, player)) return@withContext false - val data = getRaceConfig(raceId) - data.staff.add(player) - data.save(raceId) - return@withContext true - } - - suspend fun removeStaff(raceId: String, player: OfflinePlayer) = withContext(Dispatchers.IO) { - if (RaceSettingData.getOwner(raceId) == player || !existStaff(raceId, player)) { - return@withContext false //Owner can't be removed or staff can't be removed if they aren't in the list - } - val data = getRaceConfig(raceId) - data.staff.remove(player) - data.save(raceId) - return@withContext true - } - - suspend fun existStaff(raceId: String, staff: OfflinePlayer) = withContext(Dispatchers.IO) { - return@withContext getRaceConfig(raceId).staff.contains(staff) - } -} \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/files/Config.kt b/src/main/kotlin/dev/nikomaru/raceassist/files/Config.kt index d957636..59c2e3b 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/files/Config.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/files/Config.kt @@ -16,24 +16,22 @@ */ package dev.nikomaru.raceassist.files -import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigRenderOptions import dev.nikomaru.raceassist.RaceAssist.Companion.plugin -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.hocon.* +import kotlinx.serialization.* +import kotlinx.serialization.json.Json import java.io.File object Config { lateinit var config: ConfigData - const val version: String = "1.0.0" + const val version: String = "2.0.0" @ExperimentalSerializationApi fun load() { - val file = plugin.dataFolder.resolve("config.conf") + val file = plugin.dataFolder.resolve("config.json") createConfig(file) - config = hocon.decodeFromConfig(ConfigFactory.parseFile(file)) + config = json.decodeFromString(file.readText()) } @ExperimentalSerializationApi @@ -44,16 +42,16 @@ object Config { val spreadSheet = SpreadSheet(8888, arrayListOf()) val discordWebHook = DiscordWebHook(arrayListOf(), arrayListOf()) - val configData = ConfigData(version, 40, 200, discordWebHook, spreadSheet, arrayListOf(), 600) + val recordHorse = RecordHorse(13.5, 3.8) + val configData = ConfigData(version, 40, 200, discordWebHook, spreadSheet, recordHorse, arrayListOf(), null, 600000, null) - val renderOptions = ConfigRenderOptions.defaults().setOriginComments(false).setComments(false).setFormatted(true).setJson(false) - val string = hocon.encodeToConfig(configData).root().render(renderOptions) + val string = json.encodeToString(configData) if (!file.exists()) { file.createNewFile() file.writeText(string) } else { - val verNode = hocon.decodeFromConfig(ConfigFactory.parseFile(file)).version + val verNode = json.decodeFromString(file.readText()).version if (verNode != version) { file.writeText(string) } @@ -63,6 +61,9 @@ object Config { } @ExperimentalSerializationApi -private val hocon = Hocon +private val json = Json { + isLenient = true + prettyPrint = true +} diff --git a/src/main/kotlin/dev/nikomaru/raceassist/files/ConfigData.kt b/src/main/kotlin/dev/nikomaru/raceassist/files/ConfigData.kt index ff4368a..a6715d3 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/files/ConfigData.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/files/ConfigData.kt @@ -20,19 +20,39 @@ package dev.nikomaru.raceassist.files import kotlinx.serialization.Serializable @Serializable -data class ConfigData(val version: String, +data class ConfigData( + val version: String, val threshold: Int, val delay: Long, val discordWebHook: DiscordWebHook, val spreadSheet: SpreadSheet, + val recordHorse: RecordHorse, val resultWebhook: ArrayList, - val resultTimeOut: Long) + val webAPI: WebAPI?, + val raceLimitMilliSecond: Long, + val mySQL: MySQL?, +) @Serializable data class DiscordWebHook(val result: ArrayList, val betAll: ArrayList) +@Serializable +data class RecordHorse(val minSpeed: Double, val minJump: Double) + @Serializable data class ResultWebhook(val url: String, val name: String, val password: String) @Serializable -data class SpreadSheet(val port: Int, val sheetName: ArrayList) \ No newline at end of file +data class SpreadSheet(val port: Int, val sheetName: ArrayList) + +@Serializable +data class WebAPI(val port: Int, val sslPort: Int, val sslSetting: SslSetting, val jwtConfig: JWTConfig?) + +@Serializable +data class SslSetting(val keyAlias: String, val keyStorePassword: String, val privateKeyPassword: String) + +@Serializable +data class MySQL(val url: String, val username: String, val password: String) + +@Serializable +data class JWTConfig(val privateKey: String, val keyId: String, val issuer: String, val audience: ArrayList, val realm: String = "RaceAssist") \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/horse/commands/HorseDetectCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/horse/commands/HorseDetectCommand.kt new file mode 100644 index 0000000..7313ff3 --- /dev/null +++ b/src/main/kotlin/dev/nikomaru/raceassist/horse/commands/HorseDetectCommand.kt @@ -0,0 +1,23 @@ +/* + * Copyright © 2021-2022 Nikomaru + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package dev.nikomaru.raceassist.horse.commands + +import cloud.commandframework.annotations.CommandMethod + +@CommandMethod("ra horse") +class HorseDetectCommand \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/horse/commands/OwnerDeleteCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/horse/commands/OwnerDeleteCommand.kt index f7da191..cd03fbd 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/horse/commands/OwnerDeleteCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/horse/commands/OwnerDeleteCommand.kt @@ -26,7 +26,7 @@ import org.bukkit.entity.Player @CommandMethod("ra horse") class OwnerDeleteCommand { @CommandMethod("ownerDelete") - @CommandPermission("raceassist.command.ownerdelete") + @CommandPermission("raceassist.commands.horse.ownerdelete") fun removeOwner(sender: CommandSender) { if (sender !is Player) { sender.sendMessage("このコマンドはプレイヤーのみ実行できます") diff --git a/src/main/kotlin/dev/nikomaru/raceassist/horse/data/HorseData.kt b/src/main/kotlin/dev/nikomaru/raceassist/horse/data/HorseData.kt new file mode 100644 index 0000000..e68e33a --- /dev/null +++ b/src/main/kotlin/dev/nikomaru/raceassist/horse/data/HorseData.kt @@ -0,0 +1,58 @@ +/* + * Copyright © 2021-2022 Nikomaru + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package dev.nikomaru.raceassist.horse.data + +import dev.nikomaru.raceassist.data.files.UUIDSerializer +import dev.nikomaru.raceassist.web.data.History +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import java.time.ZonedDateTime +import java.util.* + +@Serializable +data class HorseData(val horse: @Serializable(with = UUIDSerializer::class) UUID, + val breeder: @Serializable(with = UUIDSerializer::class) UUID?, + val owner: @Serializable(with = UUIDSerializer::class) UUID?, + val mother: @Serializable(with = UUIDSerializer::class) UUID?, + val father: @Serializable(with = UUIDSerializer::class) UUID?, + val history: ArrayList, + val color: String, + val style: String, + val speed: Double, + val jump: Double, + val health: Double, + val name: String?, + val birthDate: @Serializable(with = KZonedDateTimeSerializer::class) ZonedDateTime?, + val lastRecordDate: @Serializable(with = KZonedDateTimeSerializer::class) ZonedDateTime, + val deathData: @Serializable(with = KZonedDateTimeSerializer::class) ZonedDateTime?) + +object KZonedDateTimeSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ZonedDateTime", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: ZonedDateTime) { + encoder.encodeString(value.toString()) + } + + override fun deserialize(decoder: Decoder): ZonedDateTime { + val string = decoder.decodeString() + return ZonedDateTime.parse(string) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/horse/events/HorseBreedEvent.kt b/src/main/kotlin/dev/nikomaru/raceassist/horse/events/HorseBreedEvent.kt index 7e271e8..f6bf650 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/horse/events/HorseBreedEvent.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/horse/events/HorseBreedEvent.kt @@ -17,13 +17,27 @@ package dev.nikomaru.raceassist.horse.events +import dev.nikomaru.raceassist.RaceAssist +import dev.nikomaru.raceassist.data.files.json +import dev.nikomaru.raceassist.files.Config +import dev.nikomaru.raceassist.horse.data.HorseData import dev.nikomaru.raceassist.horse.utlis.HorseUtils.getCalcJump +import dev.nikomaru.raceassist.horse.utlis.HorseUtils.getCalcMaxHealth import dev.nikomaru.raceassist.horse.utlis.HorseUtils.getCalcSpeed +import dev.nikomaru.raceassist.utils.Utils.client +import dev.nikomaru.raceassist.utils.Utils.toPlainText +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlinx.serialization.encodeToString +import okhttp3.* +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import org.bukkit.entity.EntityType import org.bukkit.entity.Horse import org.bukkit.event.EventHandler import org.bukkit.event.Listener import org.bukkit.event.entity.EntityBreedEvent +import java.time.ZonedDateTime class HorseBreedEvent : Listener { @EventHandler @@ -33,12 +47,53 @@ class HorseBreedEvent : Listener { } val horse = event.entity as Horse - if (horse.getCalcSpeed() < 13.7 && horse.getCalcJump() < 4.0) { + if (horse.getCalcSpeed() < Config.config.recordHorse.minSpeed && horse.getCalcJump() < Config.config.recordHorse.minJump) { return } val mother = event.mother as Horse val father = event.father as Horse + val data = HorseData(horse.uniqueId, + event.breeder?.uniqueId, + null, + mother.uniqueId, + father.uniqueId, + arrayListOf(), + horse.color.name, + horse.style.name, + horse.getCalcSpeed(), + horse.getCalcJump(), + horse.getCalcMaxHealth(), + horse.customName()?.toPlainText(), + ZonedDateTime.now(), + ZonedDateTime.now(), + null) + + if (!RaceAssist.plugin.dataFolder.resolve("horse").exists()) { + RaceAssist.plugin.dataFolder.resolve("horse").mkdirs() + } + + val file = RaceAssist.plugin.dataFolder.resolve("horse").resolve("${horse.uniqueId}.json") + val dataString = json.encodeToString(data) + + withContext(Dispatchers.IO) { file.writeText(dataString) } + + val body: RequestBody = dataString.toRequestBody("application/json; charset=utf-8".toMediaType()) + withContext(Dispatchers.IO) { + Config.config.resultWebhook.forEach { + var editUrl = it.url + if (editUrl.last() != '/') { + editUrl += "/" + } + editUrl += "v1/horse/push/" + + val request: Request = + Request.Builder().url(editUrl + horse.uniqueId).header("Authorization", Credentials.basic(it.name, it.password)).post(body) + .build() + client.newCall(request).execute().body?.close() + } + } } } + diff --git a/src/main/kotlin/dev/nikomaru/raceassist/horse/events/HorseKillEvent.kt b/src/main/kotlin/dev/nikomaru/raceassist/horse/events/HorseKillEvent.kt new file mode 100644 index 0000000..026871f --- /dev/null +++ b/src/main/kotlin/dev/nikomaru/raceassist/horse/events/HorseKillEvent.kt @@ -0,0 +1,40 @@ +/* + * Copyright © 2021-2022 Nikomaru + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package dev.nikomaru.raceassist.horse.events + +import dev.nikomaru.raceassist.horse.utlis.HorseUtils.updateKilledHorse +import org.bukkit.entity.EntityType +import org.bukkit.entity.Horse +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.entity.EntityDeathEvent + +class HorseKillEvent : Listener { + @EventHandler + suspend fun horseKillEvent(event: EntityDeathEvent) { + + val entity = event.entity + if (entity.type != EntityType.HORSE) { + return + } + val horse = entity as Horse + + updateKilledHorse(horse) + return + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/horse/utlis/HorseUtils.kt b/src/main/kotlin/dev/nikomaru/raceassist/horse/utlis/HorseUtils.kt index 30371f1..6989d15 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/horse/utlis/HorseUtils.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/horse/utlis/HorseUtils.kt @@ -17,8 +17,25 @@ package dev.nikomaru.raceassist.horse.utlis +import dev.nikomaru.raceassist.RaceAssist +import dev.nikomaru.raceassist.data.files.json +import dev.nikomaru.raceassist.files.Config +import dev.nikomaru.raceassist.horse.data.HorseData +import dev.nikomaru.raceassist.utils.Utils +import dev.nikomaru.raceassist.utils.Utils.toPlainText +import dev.nikomaru.raceassist.utils.coroutines.async +import dev.nikomaru.raceassist.web.data.History +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import okhttp3.* +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody import org.bukkit.attribute.Attribute import org.bukkit.entity.Horse +import java.time.ZonedDateTime +import java.util.* import kotlin.math.pow object HorseUtils { @@ -28,10 +45,161 @@ object HorseUtils { } fun Horse.getCalcJump(): Double { - return this.jumpStrength.pow(1.7) * 5.293; + return this.jumpStrength.pow(1.7) * 5.293 } fun Horse.getCalcMaxHealth(): Double { return this.getAttribute(Attribute.GENERIC_MAX_HEALTH)!!.value } + + fun Horse.getMotherUniqueId(): UUID? { + val uuid = this.uniqueId + val file = RaceAssist.plugin.dataFolder.resolve("horse").resolve("$uuid.json") + if (!file.exists()) { + return null + } + val data = json.decodeFromString(file.readText()) + return data.mother + } + + fun Horse.getFatherUniqueId(): UUID? { + val uuid = this.uniqueId + val file = RaceAssist.plugin.dataFolder.resolve("horse").resolve("$uuid.json") + if (!file.exists()) { + return null + } + val data = json.decodeFromString(file.readText()) + return data.father + } + + fun Horse.getBreaderUniqueId(): UUID? { + val uuid = this.uniqueId + val file = RaceAssist.plugin.dataFolder.resolve("horse").resolve("$uuid.json") + if (!file.exists()) { + return null + } + val data = json.decodeFromString(file.readText()) + return data.breeder + } + + fun Horse.getHistories(): ArrayList { + val uuid = this.uniqueId + val file = RaceAssist.plugin.dataFolder.resolve("horse").resolve("$uuid.json") + if (!file.exists()) { + return arrayListOf() + } + val data = json.decodeFromString(file.readText()) + return data.history + } + + fun Horse.getBirthDate(): ZonedDateTime? { + val uuid = this.uniqueId + val file = RaceAssist.plugin.dataFolder.resolve("horse").resolve("$uuid.json") + if (!file.exists()) { + return null + } + val data = json.decodeFromString(file.readText()) + return data.birthDate + } + + fun Horse.getDeathDate(): ZonedDateTime? { + val uuid = this.uniqueId + val file = RaceAssist.plugin.dataFolder.resolve("horse").resolve("$uuid.json") + if (!file.exists()) { + return null + } + val data = json.decodeFromString(file.readText()) + return data.deathData + } + + fun Horse.isMatchStatus(): Boolean { + if (this.getCalcSpeed() < Config.config.recordHorse.minSpeed && this.getCalcJump() < Config.config.recordHorse.minJump) { + return false + } + return true + } + + suspend fun Horse.saveData() { + val uuid = this.uniqueId + + val file = RaceAssist.plugin.dataFolder.resolve("horse").resolve("$uuid.json") + lateinit var data: HorseData + + if (file.exists()) { + val beforeData = json.decodeFromString(file.readText()) + data = beforeData.copy( + owner = this.owner?.uniqueId, + name = this.customName()?.toPlainText(), + lastRecordDate = ZonedDateTime.now(), + ) + } else { + data = HorseData(this.uniqueId, + null, + this.ownerUniqueId, + null, + null, + arrayListOf(), + this.color.name, + this.style.name, + this.getCalcSpeed(), + this.getCalcJump(), + this.getCalcMaxHealth(), + this.customName()?.toPlainText(), + null, + ZonedDateTime.now(), + null) + } + val dataString = json.encodeToString(data) + withContext(Dispatchers.async) { + file.writeText(dataString) + } + + val body: RequestBody = dataString.toRequestBody("application/json; charset=utf-8".toMediaType()) + withContext(Dispatchers.IO) { + Config.config.resultWebhook.forEach { + var editUrl = it.url + if (editUrl.last() != '/') { + editUrl += "/" + } + editUrl += "v1/horse/push/" + + val request: Request = + Request.Builder().url(editUrl + uuid).header("Authorization", Credentials.basic(it.name, it.password)).post(body).build() + Utils.client.newCall(request).execute().body?.close() + } + } + } + + suspend fun updateKilledHorse(horse: Horse) { + val uuid = horse.uniqueId + val file = RaceAssist.plugin.dataFolder.resolve("horse").resolve("${uuid}.json") + + if (!file.exists()) { + return + } + + val beforeData = json.decodeFromString(file.readText()) + val afterData = beforeData.copy(deathData = ZonedDateTime.now()) + + withContext(Dispatchers.IO) { + val dataString = json.encodeToString(afterData) + file.writeText(dataString) + val body: RequestBody = dataString.toRequestBody("application/json; charset=utf-8".toMediaType()) + Config.config.resultWebhook.forEach { + var editUrl = it.url + if (editUrl.last() != '/') { + editUrl += "/" + } + editUrl += "v1/horse/push/" + + val request: Request = + Request.Builder().url(editUrl + horse.uniqueId).header("Authorization", Credentials.basic(it.name, it.password)).post(body) + .build() + Utils.client.newCall(request).execute().body?.close() + } + } + + return + + } } \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/horse/utlis/IntegrityHorseData.kt b/src/main/kotlin/dev/nikomaru/raceassist/horse/utlis/IntegrityHorseData.kt new file mode 100644 index 0000000..19bf900 --- /dev/null +++ b/src/main/kotlin/dev/nikomaru/raceassist/horse/utlis/IntegrityHorseData.kt @@ -0,0 +1,29 @@ +/* + * Copyright © 2021-2022 Nikomaru + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package dev.nikomaru.raceassist.horse.utlis + +object IntegrityHorseData { + // listを取得し、horseDataを追加、削除する + fun getIntegrityHorseData() { + } + + // Load horse data and subsequently confirm its existence. + suspend fun confirmHorseData() { + + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/RaceJudgement.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/RaceJudgement.kt new file mode 100644 index 0000000..65d1c91 --- /dev/null +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/RaceJudgement.kt @@ -0,0 +1,548 @@ +/* + * Copyright © 2021-2022 Nikomaru + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package dev.nikomaru.raceassist.race + +import com.github.shynixn.mccoroutine.bukkit.launch +import dev.nikomaru.raceassist.RaceAssist +import dev.nikomaru.raceassist.bet.BetUtils +import dev.nikomaru.raceassist.data.files.* +import dev.nikomaru.raceassist.files.Config +import dev.nikomaru.raceassist.utils.* +import dev.nikomaru.raceassist.utils.Utils.client +import dev.nikomaru.raceassist.utils.Utils.locale +import dev.nikomaru.raceassist.utils.Utils.toLivingHorse +import dev.nikomaru.raceassist.utils.Utils.toOfflinePlayer +import dev.nikomaru.raceassist.utils.Utils.toPlainText +import dev.nikomaru.raceassist.utils.coroutines.async +import dev.nikomaru.raceassist.utils.coroutines.minecraft +import kotlinx.coroutines.* +import kotlinx.serialization.encodeToString +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer +import net.kyori.adventure.title.Title +import okhttp3.* +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import org.bukkit.Bukkit +import org.bukkit.OfflinePlayer +import org.bukkit.command.CommandSender +import org.bukkit.entity.Horse +import org.bukkit.entity.Player +import org.bukkit.scoreboard.* +import org.json.simple.JSONArray +import org.json.simple.JSONObject +import java.awt.Polygon +import java.io.OutputStream +import java.net.HttpURLConnection +import java.net.URL +import java.time.ZonedDateTime +import java.util.* +import javax.net.ssl.HttpsURLConnection +import kotlin.math.* + +class RaceJudgement(_raceId: String, _executor: CommandSender) { + + private val raceId = _raceId + private lateinit var placeId: String + private val executor = _executor + private val locale = executor.locale() + private lateinit var replacement: HashMap + + var finished = false + + private val jockeys: ArrayList = ArrayList() + private var jockeyCount = 0 + + private val finishJockeys = arrayListOf() + + private var threshold = 0 + + private var centralXPoint = 0 + private var centralYPoint = 0 + private var goalDegree = 0 + private var lap: Int = 0 + private var reverse: Boolean = false + + private var innerCircumference: Double = 0.0 + + private lateinit var insidePolygon: Polygon + private lateinit var outsidePolygon: Polygon + + private var startDegree = 0 + + private val totalDegree = hashMapOf() + private val beforeDegree = hashMapOf() + private val currentLap = hashMapOf() + private val passBorders = hashMapOf() + + private val time = hashMapOf() + + private val audiences = RaceAudience() + + var suspend = false + + private var limit = 0L + + private lateinit var raceResultData: RaceResultData + + private var beforeTime = 0L + + suspend fun setting(): Boolean { + placeId = RaceSettingData.getPlaceId(raceId) + if (PlaceSettingData.getInsidePolygon(placeId).npoints == 0 || PlaceSettingData.getOutsidePolygon(placeId).npoints == 0) { + executor.sendMessage(Lang.getComponent("no-exist-race", locale)) + return false + } + + RaceSettingData.getJockeys(raceId).forEach { + if (it.isOnline) { + jockeys.add(it as Player) + executor.sendMessage(Lang.getComponent("player-join", locale, it.name)) + } else { + executor.sendMessage(Lang.getComponent("player-is-offline", locale, it.name)) + } + } + if (jockeys.size < 2) { + executor.sendMessage(Lang.getComponent("over-two-users-need", locale)) + return false + } + + centralXPoint = PlaceSettingData.getCentralXPoint(placeId) ?: run { + executor.sendMessage(Lang.getComponent("no-exist-central-point", locale)) + return false + } + + centralYPoint = PlaceSettingData.getCentralYPoint(placeId) ?: run { + executor.sendMessage(Lang.getComponent("no-exist-central-point", locale)) + return false + } + + goalDegree = PlaceSettingData.getGoalDegree(placeId) + + jockeyCount = jockeys.size + threshold = Config.config.threshold + lap = RaceSettingData.getLap(raceId) + reverse = PlaceSettingData.getReverse(placeId) + innerCircumference = getInnerCircumference(insidePolygon) + + + insidePolygon = PlaceSettingData.getInsidePolygon(placeId) + outsidePolygon = PlaceSettingData.getOutsidePolygon(placeId) + + limit = Config.config.raceLimitMilliSecond + + //観客(スコアボードを表示する人)の設定 + Utils.audience[raceId]?.forEach { + audiences.add(Bukkit.getOfflinePlayer(it)) + } + jockeys.forEach { + audiences.add(it) + } + if (executor is Player) { + audiences.add(executor) + } + + replacement = RaceSettingData.getReplacement(raceId) + RaceSettingData.getHorse(raceId).forEach { (t, u) -> + val name = u.toLivingHorse()?.customName()?.toPlainText() + if (name != null) { + replacement[t] = name + } + } + + return true + } + + suspend fun start() { + + var timer = 0 + while (timer <= 4) { + audiences.showTitle(Title.title(Component.text("${5 - timer}"), Component.text(""))) + delay(1000) + timer++ + } + + jockeys.forEach { + beforeDegree[it.uniqueId] = Utils.getRaceDegree(if (!reverse) (it.location.blockX - centralXPoint).toDouble() + else (-1 * (it.location.blockX - centralXPoint)).toDouble(), (it.location.blockZ - centralYPoint).toDouble()) + currentLap[it.uniqueId] = 0 + passBorders[it.uniqueId] = 0 + } + + beforeTime = System.currentTimeMillis() + audiences.showTitleI18n("race-start") + + //最初の位置をランダムに選んだ人から決める + val randomJockey = jockeys.random() + val startDegree = getStartPoint(randomJockey, centralYPoint, reverse, centralXPoint) + + // 結果の保存のため + val goalDistance = getGoalDistance(lap, goalDegree, startDegree, innerCircumference) + + val senderName = if (executor is Player) executor.name else "Console" + + val uuidToName = jockeys.associate { it.uniqueId to it.name } as HashMap + + val rectangleData = RectangleData(outsidePolygon.bounds2D.minX.roundToInt() - 4, + outsidePolygon.bounds2D.minY.roundToInt() - 4, + outsidePolygon.bounds2D.maxX.roundToInt() + 4, + outsidePolygon.bounds2D.maxY.roundToInt() + 4) + val horses: HashMap = HashMap() + jockeys.forEach { + val vehicle = it.vehicle + if (vehicle != null) { + if (vehicle is Horse) { + horses[it.uniqueId] = vehicle.uniqueId + } + } + } + + raceResultData = RaceResultData("1.0", + raceId, + senderName, + horses, + ZonedDateTime.now(), + ZonedDateTime.now(), + false, + hashMapOf(), + lap, + goalDistance, + uuidToName, + replacement, + rectangleData, + insidePolygon, + outsidePolygon, + arrayListOf(), + null) + + } + + private fun getStartPoint(randomJockey: Player, centralYPoint: Int, reverse: Boolean, centralXPoint: Int) = + Utils.getRaceDegree((randomJockey.location.blockZ - centralYPoint).toDouble(), if (reverse) { + (-1 * (randomJockey.location.blockX - centralXPoint)).toDouble() + } else { + (randomJockey.location.blockX - centralXPoint).toDouble() + }) + + private suspend fun putRaceResult(raceResultData: RaceResultData) { + withContext(Dispatchers.IO) { + val resultFolder = RaceAssist.plugin.dataFolder.resolve("result") + resultFolder.mkdirs() + val resultFile = resultFolder.resolve("${raceResultData.raceId}.json") + resultFile.writeText(json.encodeToString(raceResultData)) + sendResultWebHook(raceResultData) + } + } + + private fun sendResultWebHook(raceResultData: RaceResultData) { + val json = json.encodeToString(raceResultData) + val body: RequestBody = json.toRequestBody("application/json; charset=utf-8".toMediaType()) + Config.config.resultWebhook.forEach { + var editUrl = it.url + if (editUrl.last() != '/') { + editUrl += "/" + } + editUrl += "v1/result/push/" + + val request: Request = + Request.Builder().url(editUrl + raceResultData.raceId).header("Authorization", Credentials.basic(it.name, it.password)).post(body) + .build() + client.newCall(request).execute().body?.close() + } + } + + private suspend fun getInnerCircumference(insidePolygon: Polygon) = withContext(Dispatchers.Default) { + //内周の距離のカウント + var total = 0.0 + val insideX = insidePolygon.xpoints + val insideY = insidePolygon.ypoints + for (i in 0 until insidePolygon.npoints) { + total += if (i <= insidePolygon.npoints - 2) { + hypot((insideX[i] - insideX[i + 1]).toDouble(), (insideY[i] - insideY[i + 1]).toDouble()) + } else { + hypot((insideX[i] - insideX[0]).toDouble(), (insideY[i] - insideY[0]).toDouble()) + } + } + total + } + + private suspend fun sendWebHook(finishJockey: ArrayList, + time: HashMap, + starter: OfflinePlayer, + raceId: String, + suspend: Boolean) { + val json = JSONObject() + json["username"] = "RaceAssist" + json["avatar_url"] = "https://3.bp.blogspot.com/-Y3AVYVjLcPs/UYiNxIliDxI/AAAAAAAARSg/nZLIqBRUta8/s800/animal_uma.png" + val result = JSONArray() + val embeds = JSONArray() + val author = JSONObject() + val embedsObject = JSONObject() + embedsObject["title"] = if (suspend) "RaceAssist_suspend" else "RaceAssist" + author["name"] = Lang.getText("discord-webhook-name", Locale.getDefault(), starter.name, raceId) + author["icon_url"] = "https://crafthead.net/avatar/$starter" + embedsObject["author"] = author + for (i in 0 until finishJockey.size) { + val playerResult = JSONObject() + playerResult["name"] = Lang.getText("discord-webhook-ranking", Locale.getDefault(), i + 1) + playerResult["value"] = String.format("%s %2d:%02d", + Bukkit.getPlayer(finishJockey[i])?.name, + floor((time[finishJockey[i]]!!.toDouble() / 60000)).toInt(), + time[finishJockey[i]]!! % 60000) + playerResult["inline"] = true + result.add(playerResult) + } + embedsObject["fields"] = result + embeds.add(embedsObject) + json["embeds"] = embeds + + sendDiscordResultWebHook(json.toString()) + } + + private suspend fun sendDiscordResultWebHook(json: String) = withContext(Dispatchers.IO) { + + Config.config.discordWebHook.result.forEach { + try { + val webHookUrl = URL(it) + val con: HttpsURLConnection = (webHookUrl.openConnection() as HttpsURLConnection) + + con.addRequestProperty("Content-Type", "application/JSON; charset=utf-8") + con.addRequestProperty("User-Agent", "DiscordBot") + con.doOutput = true + con.requestMethod = "POST" + + con.setRequestProperty("Content-Length", json.length.toString()) + + val stream: OutputStream = con.outputStream + stream.write(json.toByteArray(Charsets.UTF_8)) + stream.flush() + stream.close() + + val status: Int = con.responseCode + if (status != HttpURLConnection.HTTP_OK && status != HttpURLConnection.HTTP_NO_CONTENT) { + RaceAssist.plugin.logger.warning("error:$status") + } + con.disconnect() + + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + private fun displayScoreboard(nowRankings: List, + currentDegree: HashMap, + raceAudience: Collection, + innerCircumference: Int, + startDegree: Int, + goalDegree: Int, + lap: Int) { + + raceAudience.forEach { + + if (Bukkit.getOfflinePlayer(it).isOnline) { + val player = Bukkit.getPlayer(it)!! + val manager: ScoreboardManager = Bukkit.getScoreboardManager() + val scoreboard = manager.newScoreboard + val objective: Objective = scoreboard.registerNewObjective(Lang.getText("scoreboard-ranking", player.locale()), + "dummy", + Lang.getComponent("scoreboard-now-ranking", player.locale())) + objective.displaySlot = DisplaySlot.SIDEBAR + + val goalDistance = getGoalDistance(lap, goalDegree, startDegree, innerCircumference.toDouble()) + + for (i in nowRankings.indices) { + + val playerName = replacement[nowRankings[i]] ?: Bukkit.getPlayer(nowRankings[i])?.name + + val component = if (currentDegree[Bukkit.getPlayer(nowRankings[i])!!.uniqueId] == null) { + Lang.getComponent("scoreboard-now-ranking-and-name", player.locale(), i + 1, playerName) + .append(Lang.getComponent("finished-the-race", player.locale())) + } else { + val currentDistance = + ((currentDegree[Bukkit.getPlayer(nowRankings[i])!!.uniqueId]!!.toDouble() - startDegree.toDouble()) / 360.0 * innerCircumference.toDouble()).toInt() + + Lang.getComponent("scoreboard-now-ranking-and-name", player.locale(), i + 1, playerName) + .append(Lang.mm.deserialize("${currentDistance}m/${goalDistance}m ")) + } + + val displayDegree = objective.getScore(LegacyComponentSerializer.legacySection().serialize(component)) + displayDegree.score = nowRankings.size - i + } + player.scoreboard = scoreboard + } + } + } + + private fun getGoalDistance(lap: Int, goalDegree: Int, startDegree: Int, innerCircumference: Double) = + (((lap - 1).toDouble() + if (goalDegree > startDegree) { + ((goalDegree.toDouble() - startDegree.toDouble()) / 360.0) + } else { + ((goalDegree.toDouble() + 360.0 - startDegree.toDouble()) / 360.0) + }) * innerCircumference).toInt() + + private fun decideRanking(totalDegree: HashMap): ArrayList { + val ranking = ArrayList() + val sorted = totalDegree.toList().sortedBy { (_, value) -> value } + sorted.forEach { + ranking.add(it.first) + } + ranking.reverse() + return ranking + } + + suspend fun calculate() { + val currentRaceData = CurrentRaceData(((System.currentTimeMillis() - beforeTime).toDouble() / 1000), arrayListOf()) + + //正常時の終了 + if (jockeys.size < 1) { + finished = true + return + } + //stopコマンドによる終了 + if (Utils.stop[raceId] == true) { + suspend = true + raceResultData.suspend = true + audiences.sendMessageI18n("suspended-race-by-operator") + finished = true + return + } + //制限による終了 + if ((System.currentTimeMillis() - beforeTime) > limit) { + suspend = true + raceResultData.suspend = true + audiences.sendMessageI18n("suspended-race-by-limit") + finished = true + return + } + + val iterator = jockeys.iterator() + + while (iterator.hasNext()) { + val player: Player = iterator.next() + + if (!player.isOnline) { + iterator.remove() + continue + } + //各騎手の位置の取得 + val nowX = withContext(Dispatchers.minecraft) { player.location.blockX } + val nowY = withContext(Dispatchers.minecraft) { player.location.blockZ } + val relativeNowX = if (!reverse) nowX - centralXPoint else -1 * (nowX - centralXPoint) + val relativeNowY = nowY - centralYPoint + val currentDegree = Utils.getRaceDegree(relativeNowY.toDouble(), relativeNowX.toDouble()) + val uuid = player.uniqueId + val beforeLap = currentLap[uuid] + + //ラップの計算 + currentLap[uuid] = currentLap[uuid]!! + Utils.judgeLap(goalDegree, beforeDegree[uuid], currentDegree, threshold) + passBorders[uuid] = passBorders[uuid]!! + Utils.judgeLap(0, beforeDegree[uuid], currentDegree, threshold) + Utils.displayLap(currentLap[uuid], beforeLap, player, lap) + beforeDegree[uuid] = currentDegree + totalDegree[uuid] = currentDegree + (passBorders[uuid]!! * 360) + + val currentDistance = (((totalDegree[uuid]!!.toDouble() - startDegree.toDouble()) / 360.0) * innerCircumference).toInt() + + val currentResultData = PlayerRaceData(uuid, false, currentDistance, nowX, nowY) + currentRaceData.playerRaceData.add(currentResultData) + + //コース内にいるか判断 + if (insidePolygon.contains(nowX, nowY) || !outsidePolygon.contains(nowX, nowY)) { + player.sendActionBar(Lang.getComponent("outside-the-racetrack", player.locale())) + } + + //ゴールした時の処理 + if (currentLap[uuid]!! >= lap) { + withContext(Dispatchers.async) { + iterator.remove() + finishJockeys.add(uuid) + totalDegree.remove(uuid) + currentLap.remove(uuid) + player.showTitle(Title.title(Lang.getComponent("player-ranking", player.locale(), jockeyCount - jockeys.size, jockeyCount), + Component.text(""))) + } + time[uuid] = (System.currentTimeMillis() - beforeTime) + continue + } + + } + + finishJockeys.forEach { finishJockey -> + val player = Bukkit.getPlayer(finishJockey) ?: return@forEach + val uuid = player.uniqueId + val none = currentRaceData.playerRaceData.none { it.uuid == uuid } + if (none) { + currentRaceData.playerRaceData.add(PlayerRaceData(uuid, true, null, null, null)) + } + } + //現在のレースの状況保存のため + raceResultData.currentRaceData.add(currentRaceData) + + } + + suspend fun display() { + //順位の表示 + RaceAssist.plugin.launch { + val displayRanking = async(Dispatchers.minecraft) { + displayScoreboard(finishJockeys.plus(decideRanking(totalDegree)), + totalDegree, + audiences.getUUID(), + innerCircumference.roundToInt(), + startDegree, + goalDegree, + lap) + } + delay(Config.config.delay) + displayRanking.await() + }.join() + } + + suspend fun last() { + //終了時の処理 + raceResultData.finish = ZonedDateTime.now() + audiences.showTitleI18n("finish-race") + delay(1000) + + //後始末 + Bukkit.getOnlinePlayers().forEach { + it.scoreboard.clearSlot(DisplaySlot.SIDEBAR) + } + + for (i in 0 until finishJockeys.size) { + audiences.sendMessageI18n("to-notice-ranking-message", i + 1, Bukkit.getPlayer(finishJockeys[i])?.name!!) + } + + + finishJockeys.forEachIndexed { index, element -> + raceResultData.result[index + 1] = element + } + raceResultData.image = Utils.createImage(raceResultData.rectangleData.x1, + raceResultData.rectangleData.x2, + raceResultData.rectangleData.y1, + raceResultData.rectangleData.y2) + + //結果の保存 + putRaceResult(raceResultData) + sendWebHook(finishJockeys, time, RaceSettingData.getOwner(raceId), raceId, suspend) + } + + suspend fun payDividend() { + BetUtils.payDividend(finishJockeys[0].toOfflinePlayer(), raceId, executor, executor.locale()) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/RaceResultData.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/RaceResultData.kt index 1ef1a00..8bffe27 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/RaceResultData.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/RaceResultData.kt @@ -19,19 +19,19 @@ package dev.nikomaru.raceassist.race import dev.nikomaru.raceassist.data.files.PolygonSerializer import dev.nikomaru.raceassist.data.files.UUIDSerializer -import kotlinx.datetime.LocalDateTime +import dev.nikomaru.raceassist.horse.data.KZonedDateTimeSerializer import kotlinx.serialization.Serializable import java.awt.Polygon +import java.time.ZonedDateTime import java.util.* @Serializable data class RaceResultData(val ver: String = "1.0", val raceId: String, - val raceUniqueId: String, val administrator: String, val horse: HashMap<@Serializable(with = UUIDSerializer::class) UUID, @Serializable(with = UUIDSerializer::class) UUID>, - val start: LocalDateTime, - var finish: LocalDateTime, + val start: @Serializable(with = KZonedDateTimeSerializer::class) ZonedDateTime, + var finish: @Serializable(with = KZonedDateTimeSerializer::class) ZonedDateTime, var suspend: Boolean, val result: HashMap, val lap: Int, @@ -41,10 +41,11 @@ data class RaceResultData(val ver: String = "1.0", val rectangleData: RectangleData, val insidePolygon: @Serializable(with = PolygonSerializer::class) Polygon, val outsidePolygon: @Serializable(with = PolygonSerializer::class) Polygon, - val currentRaceData: ArrayList) + val currentRaceData: ArrayList, + var image: String?) @Serializable -data class CurrentRacaData( +data class CurrentRaceData( val time: Double, val playerRaceData: ArrayList, ) diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/HelpCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/HelpCommand.kt index 1d30291..950b816 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/HelpCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/HelpCommand.kt @@ -24,11 +24,24 @@ import org.bukkit.command.CommandSender @CommandMethod("ra|raceassist") class HelpCommand { @CommandMethod("help") - @CommandPermission("raceassist.command.help") + @CommandPermission("raceassist.commands.help") @CommandDescription("help command") fun help(sender: CommandSender) { val message = "コマンドリスト クリックで開く" sender.sendMessage(mm.deserialize(message)) } +// +// @CommandMethod("image ") +// suspend fun createImage(sender: CommandSender, +// @Argument(value = "x1") x1: Int, +// @Argument(value = "x2") x2: Int, +// @Argument(value = "y1") y1: Int, +// @Argument(value = "y2") y2: Int) { +// val base64 = Utils.createImage(x1, x2, y1, y2) +// +// File("D:\\download\\racajkghfds.txt").writeText(base64) +// val serializedObject: ByteArray = Base64.getDecoder().decode(base64) +// File("D:\\download\\racajkghfds.png").writeBytes(serializedObject) +// } } \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/ReloadCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/ReloadCommand.kt index 6bef533..f039f9d 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/ReloadCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/ReloadCommand.kt @@ -27,7 +27,7 @@ class ReloadCommand { @OptIn(ExperimentalSerializationApi::class) @CommandMethod("reload") - @CommandPermission("raceassist.command.reload") + @CommandPermission("raceassist.commands.reload") @CommandDescription("help command") fun reload(sender: CommandSender) { Config.load() diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/audience/AudienceJoinCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/audience/AudienceJoinCommand.kt index fb08baa..97620b3 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/audience/AudienceJoinCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/audience/AudienceJoinCommand.kt @@ -28,7 +28,7 @@ import org.bukkit.entity.Player class AudienceJoinCommand { @CommandPermission("raceassist.commands.audience.join") @CommandMethod("join ") - fun join(sender: CommandSender, @Argument(value = "raceId", suggestions = "raceId") raceId: String) { + suspend fun join(sender: CommandSender, @Argument(value = "raceId", suggestions = "raceId") raceId: String) { if (sender !is Player) { sender.sendMessage("Only the player can do this.") return diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/audience/AudienceListCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/audience/AudienceListCommand.kt index 62445a8..2d449ba 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/audience/AudienceListCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/audience/AudienceListCommand.kt @@ -18,21 +18,21 @@ package dev.nikomaru.raceassist.race.commands.audience import cloud.commandframework.annotations.* +import dev.nikomaru.raceassist.data.files.RaceUtils import dev.nikomaru.raceassist.utils.Lang import dev.nikomaru.raceassist.utils.Utils import dev.nikomaru.raceassist.utils.Utils.locale -import dev.nikomaru.raceassist.utils.Utils.returnCanRaceSetting import org.bukkit.Bukkit import org.bukkit.command.CommandSender @CommandMethod("ra|RaceAssist audience") class AudienceListCommand { @CommandPermission("raceassist.commands.audience.list") - @CommandMethod("list ") - suspend fun list(sender: CommandSender, @Argument(value = "raceId", suggestions = "raceId") raceId: String) { + @CommandMethod("list ") + suspend fun list(sender: CommandSender, @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String) { val locale = sender.locale() - if (returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return sender.sendMessage(Lang.getComponent("participants-list", locale)) Utils.audience[raceId]?.forEach { sender.sendMessage(Bukkit.getOfflinePlayer(it).name.toString()) diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceCentralCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceCentralCommand.kt index b59637f..8df3674 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceCentralCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceCentralCommand.kt @@ -18,26 +18,26 @@ package dev.nikomaru.raceassist.race.commands.place import cloud.commandframework.annotations.* +import dev.nikomaru.raceassist.data.files.RaceUtils import dev.nikomaru.raceassist.utils.Lang import dev.nikomaru.raceassist.utils.Utils.canSetCentral -import dev.nikomaru.raceassist.utils.Utils.centralRaceId -import dev.nikomaru.raceassist.utils.Utils.returnCanRaceSetting +import dev.nikomaru.raceassist.utils.Utils.centralPlaceId import org.bukkit.command.CommandSender import org.bukkit.entity.Player @CommandMethod("ra|RaceAssist place") class PlaceCentralCommand { @CommandPermission("raceassist.commands.place.central") - @CommandMethod("central ") - suspend fun central(sender: CommandSender, @Argument(value = "raceId", suggestions = "raceId") raceId: String) { + @CommandMethod("central ") + suspend fun central(sender: CommandSender, @Argument(value = "operatePlaceId", suggestions = "operatePlaceId") placeId: String) { if (sender !is Player) { sender.sendMessage("Only the player can do this.") return } - if (returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasPlaceControlPermission(placeId, sender)) return canSetCentral[sender.uniqueId] = true - centralRaceId[sender.uniqueId] = raceId + centralPlaceId[sender.uniqueId] = placeId sender.sendMessage(Lang.getComponent("to-set-central-point", sender.locale())) } diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceCreateCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceCreateCommand.kt new file mode 100644 index 0000000..9aa560a --- /dev/null +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceCreateCommand.kt @@ -0,0 +1,42 @@ +/* + * Copyright © 2021-2022 Nikomaru + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package dev.nikomaru.raceassist.race.commands.place + +import cloud.commandframework.annotations.* +import dev.nikomaru.raceassist.data.files.PlaceSettingData +import dev.nikomaru.raceassist.utils.Lang +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player + +@CommandMethod("ra|RaceAssist place") +class PlaceCreateCommand { + + @CommandPermission("raceassist.commands.place.reverse") + @CommandMethod("create ") + suspend fun reverse(sender: CommandSender, @Argument(value = "placeId") @Regex(value = "[^_]+_\\d+$") placeId: String) { + if (sender !is Player) { + sender.sendMessage("Only the player can do this.") + return + } + + PlaceSettingData.createPlace(placeId, sender) + + sender.sendMessage(Lang.getComponent("to-create-new-placeId", sender.locale())) + + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceDegreeCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceDegreeCommand.kt index 2c126fd..7e668c6 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceDegreeCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceDegreeCommand.kt @@ -20,27 +20,27 @@ package dev.nikomaru.raceassist.race.commands.place import cloud.commandframework.annotations.* import dev.nikomaru.raceassist.data.files.PlaceSettingData import dev.nikomaru.raceassist.data.files.PlaceSettingData.getReverse +import dev.nikomaru.raceassist.data.files.RaceUtils import dev.nikomaru.raceassist.utils.Lang import dev.nikomaru.raceassist.utils.Utils.getCentralPoint import dev.nikomaru.raceassist.utils.Utils.getRaceDegree -import dev.nikomaru.raceassist.utils.Utils.returnCanRaceSetting import org.bukkit.command.CommandSender import org.bukkit.entity.Player @CommandMethod("ra|RaceAssist place") class PlaceDegreeCommand { @CommandPermission("raceassist.commands.place.degree") - @CommandMethod("degree ") - suspend fun degree(sender: CommandSender, @Argument(value = "raceId", suggestions = "raceId") raceId: String) { + @CommandMethod("degree ") + suspend fun degree(sender: CommandSender, @Argument(value = "operatePlaceId", suggestions = "operatePlaceId") placeId: String) { if (sender !is Player) { sender.sendMessage("Only the player can do this.") return } - if (returnCanRaceSetting(raceId, sender)) return - val centralXPoint = getCentralPoint(raceId, true) ?: return sender.sendMessage(Lang.getComponent("no-exist-central-point", sender.locale())) - val centralYPoint = getCentralPoint(raceId, false) ?: return sender.sendMessage(Lang.getComponent("no-exist-central-point", sender.locale())) - val reverse = getReverse(raceId) + if (!RaceUtils.hasRaceControlPermission(placeId, sender)) return + val centralXPoint = getCentralPoint(placeId, true) ?: return sender.sendMessage(Lang.getComponent("no-exist-central-point", sender.locale())) + val centralYPoint = getCentralPoint(placeId, false) ?: return sender.sendMessage(Lang.getComponent("no-exist-central-point", sender.locale())) + val reverse = getReverse(placeId) val nowX = sender.location.blockX val nowY = sender.location.blockZ val relativeNowX = if (!reverse) nowX - centralXPoint else -1 * (nowX - centralXPoint) @@ -50,24 +50,29 @@ class PlaceDegreeCommand { in 0..45 -> { 0 } + in 46..135 -> { 90 } + in 136..225 -> { 180 } + in 226..315 -> { 270 } + in 316..360 -> { 0 } + else -> { 0 } } sender.sendMessage(Lang.getComponent("to-set-degree", sender.locale(), degree)) - PlaceSettingData.setGoalDegree(raceId, degree) + PlaceSettingData.setGoalDegree(placeId, degree) } } \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceReverseCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceReverseCommand.kt index 6ceee4e..385f70c 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceReverseCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceReverseCommand.kt @@ -19,23 +19,23 @@ package dev.nikomaru.raceassist.race.commands.place import cloud.commandframework.annotations.* import dev.nikomaru.raceassist.data.files.PlaceSettingData +import dev.nikomaru.raceassist.data.files.RaceUtils.hasRaceControlPermission import dev.nikomaru.raceassist.utils.Lang -import dev.nikomaru.raceassist.utils.Utils.returnCanRaceSetting import org.bukkit.command.CommandSender import org.bukkit.entity.Player @CommandMethod("ra|RaceAssist place") class PlaceReverseCommand { @CommandPermission("raceassist.commands.place.reverse") - @CommandMethod("reverse ") - suspend fun reverse(sender: CommandSender, @Argument(value = "raceId", suggestions = "raceId") raceId: String) { + @CommandMethod("reverse ") + suspend fun reverse(sender: CommandSender, @Argument(value = "operatePlaceId", suggestions = "operatePlaceId") placeId: String) { if (sender !is Player) { sender.sendMessage("Only the player can do this.") return } - if (returnCanRaceSetting(raceId, sender)) return + if (!hasRaceControlPermission(placeId, sender)) return - PlaceSettingData.setReverse(raceId, !PlaceSettingData.getReverse(raceId)) + PlaceSettingData.setReverse(placeId, !PlaceSettingData.getReverse(placeId)) sender.sendMessage(Lang.getComponent("to-change-race-orientation", sender.locale())) diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceSetCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceSetCommand.kt index 2435fda..9652a2c 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceSetCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceSetCommand.kt @@ -18,32 +18,28 @@ package dev.nikomaru.raceassist.race.commands.place import cloud.commandframework.annotations.* -import dev.nikomaru.raceassist.data.files.RaceSettingData +import dev.nikomaru.raceassist.data.files.RaceUtils import dev.nikomaru.raceassist.utils.Lang import dev.nikomaru.raceassist.utils.Utils.canSetInsideCircuit import dev.nikomaru.raceassist.utils.Utils.canSetOutsideCircuit import dev.nikomaru.raceassist.utils.Utils.circuitRaceId import dev.nikomaru.raceassist.utils.Utils.getInsideRaceExist -import dev.nikomaru.raceassist.utils.Utils.returnCanRaceSetting import org.bukkit.command.CommandSender import org.bukkit.entity.Player @CommandMethod("ra|RaceAssist place") class PlaceSetCommand { @CommandPermission("raceassist.commands.place.set") - @CommandMethod("set ") + @CommandMethod("set ") suspend fun set(sender: CommandSender, - @Argument(value = "raceId", suggestions = "raceId") raceId: String, + @Argument(value = "operatePlaceId", suggestions = "operatePlaceId") placeId: String, @Argument(value = "type", suggestions = "placeType") type: String) { if (sender !is Player) { sender.sendMessage("Only the player can do this.") return } - if (!RaceSettingData.existsRace(raceId)) { - sender.sendMessage(Lang.getComponent("no-exist-race", sender.locale())) - return - } else if (returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasPlaceControlPermission(placeId, sender)) return if (canSetOutsideCircuit[sender.uniqueId] != null || canSetInsideCircuit[sender.uniqueId] != null) { sender.sendMessage(Lang.getComponent("already-setting-mode", sender.locale())) @@ -53,14 +49,14 @@ class PlaceSetCommand { canSetInsideCircuit[sender.uniqueId] = true sender.sendMessage(Lang.getComponent("to-be-inside-set-mode", sender.locale())) } else if (type == "out") { - if (!getInsideRaceExist(raceId)) { + if (!getInsideRaceExist(placeId)) { sender.sendMessage(Lang.getComponent("no-inside-course-set", sender.locale())) return } canSetOutsideCircuit[sender.uniqueId] = true sender.sendMessage(Lang.getComponent("to-be-outside-set-mode", sender.locale())) } - circuitRaceId[sender.uniqueId] = raceId + circuitRaceId[sender.uniqueId] = placeId sender.sendMessage(Lang.getComponent("to-click-left-start-right-finish", sender.locale())) sender.sendMessage(Lang.getComponent("to-enter-finish-message", sender.locale())) diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceStaffCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceStaffCommand.kt new file mode 100644 index 0000000..55dfc41 --- /dev/null +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceStaffCommand.kt @@ -0,0 +1,77 @@ +/* + * Copyright © 2021-2022 Nikomaru + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package dev.nikomaru.raceassist.race.commands.place + +import cloud.commandframework.annotations.* +import dev.nikomaru.raceassist.data.files.* +import dev.nikomaru.raceassist.utils.Lang +import dev.nikomaru.raceassist.utils.Utils.locale +import org.bukkit.Bukkit +import org.bukkit.command.CommandSender + +@CommandMethod("ra|raceassist place") +@CommandPermission("raceassist.commands.place.staff") + +class PlaceStaffCommand { + @CommandMethod("staff add ") + suspend fun addStaff(sender: CommandSender, + @Argument(value = "operatePlaceId", suggestions = "operatePlaceId") PlaceId: String, + @Argument(value = "playerName", suggestions = "playerName") playerName: String) { + + if (!RaceUtils.hasPlaceControlPermission(PlaceId, sender)) return + + val locale = sender.locale() + val target = Bukkit.getOfflinePlayerIfCached(playerName) ?: return sender.sendMessage(Lang.getComponent("player-add-not-exist", locale)) + + if (PlaceSettingData.addStaff(PlaceId, target)) { + sender.sendMessage(Lang.getComponent("add-staff", locale)) + } else { + sender.sendMessage(Lang.getComponent("already-added-staff", locale)) + } + + } + + @CommandMethod("staff remove ") + suspend fun removeStaff(sender: CommandSender, + @Argument(value = "operatePlaceId", suggestions = "operatePlaceId") PlaceId: String, + @Argument(value = "playerName", suggestions = "playerName") playerName: String) { + if (!RaceUtils.hasPlaceControlPermission(PlaceId, sender)) return + + val locale = sender.locale() + val target = Bukkit.getOfflinePlayerIfCached(playerName) ?: return sender.sendMessage(Lang.getComponent("player-add-not-exist", locale)) + if (PlaceSettingData.getOwner(PlaceId) == target) { + return sender.sendMessage(Lang.getComponent("cant-remove-yourself-staff", locale)) + } + + if (PlaceSettingData.removeStaff(PlaceId, target)) { + sender.sendMessage(Lang.getComponent("delete-staff", locale)) + } else { + sender.sendMessage(Lang.getComponent("not-find-staff", locale)) + } + + } + + @CommandMethod("staff list ") + suspend fun listStaff(sender: CommandSender, @Argument(value = "operatePlaceId", suggestions = "operatePlaceId") PlaceId: String) { + if (!RaceUtils.hasPlaceControlPermission(PlaceId, sender)) return + PlaceSettingData.getStaffs(PlaceId).forEach { + sender.sendMessage(it.name.toString()) + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/player/PlayerAddCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/player/PlayerAddCommand.kt index a650a35..a9028e6 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/player/PlayerAddCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/player/PlayerAddCommand.kt @@ -19,8 +19,8 @@ package dev.nikomaru.raceassist.race.commands.player import cloud.commandframework.annotations.* import dev.nikomaru.raceassist.data.files.RaceSettingData +import dev.nikomaru.raceassist.data.files.RaceUtils import dev.nikomaru.raceassist.utils.Lang -import dev.nikomaru.raceassist.utils.Utils import dev.nikomaru.raceassist.utils.Utils.locale import org.bukkit.Bukkit import org.bukkit.OfflinePlayer @@ -30,11 +30,11 @@ import org.bukkit.command.CommandSender class PlayerAddCommand { @CommandPermission("raceassist.commands.player.add") - @CommandMethod("add ") + @CommandMethod("add ") suspend fun addPlayer(sender: CommandSender, - @Argument(value = "raceId", suggestions = "raceId") raceId: String, + @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String, @Argument(value = "playerName", suggestions = "playerName") playerName: String) { - if (Utils.returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return val locale = sender.locale() diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/player/PlayerDeleteCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/player/PlayerDeleteCommand.kt index 9e1a1f9..ca9630c 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/player/PlayerDeleteCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/player/PlayerDeleteCommand.kt @@ -19,8 +19,8 @@ package dev.nikomaru.raceassist.race.commands.player import cloud.commandframework.annotations.* import dev.nikomaru.raceassist.data.files.RaceSettingData +import dev.nikomaru.raceassist.data.files.RaceUtils import dev.nikomaru.raceassist.utils.Lang -import dev.nikomaru.raceassist.utils.Utils import dev.nikomaru.raceassist.utils.Utils.locale import org.bukkit.command.CommandSender @@ -28,10 +28,10 @@ import org.bukkit.command.CommandSender class PlayerDeleteCommand { @CommandPermission("raceassist.commands.player.delete") - @CommandMethod("delete ") - suspend fun deletePlayer(sender: CommandSender, @Argument(value = "raceId", suggestions = "raceId") raceId: String) { + @CommandMethod("delete ") + suspend fun deletePlayer(sender: CommandSender, @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String) { - if (Utils.returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return RaceSettingData.getJockeys(raceId).forEach { RaceSettingData.removeJockey(raceId, it) diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/player/PlayerListCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/player/PlayerListCommand.kt index 7bdd08c..d245b0a 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/player/PlayerListCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/player/PlayerListCommand.kt @@ -19,17 +19,17 @@ package dev.nikomaru.raceassist.race.commands.player import cloud.commandframework.annotations.* import dev.nikomaru.raceassist.data.files.RaceSettingData -import dev.nikomaru.raceassist.utils.Utils +import dev.nikomaru.raceassist.data.files.RaceUtils import org.bukkit.command.CommandSender @CommandMethod("ra|RaceAssist player") class PlayerListCommand { @CommandPermission("raceassist.commands.player.list") - @CommandMethod("list ") - suspend fun displayPlayerList(sender: CommandSender, @Argument(value = "raceId", suggestions = "raceId") raceId: String) { + @CommandMethod("list ") + suspend fun displayPlayerList(sender: CommandSender, @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String) { - if (Utils.returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return if (RaceSettingData.getJockeys(raceId).isEmpty()) { sender.sendMessage("プレイヤーはいません") } diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/player/PlayerRemoveCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/player/PlayerRemoveCommand.kt index 478a256..3d8f65c 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/player/PlayerRemoveCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/player/PlayerRemoveCommand.kt @@ -19,8 +19,8 @@ package dev.nikomaru.raceassist.race.commands.player import cloud.commandframework.annotations.* import dev.nikomaru.raceassist.data.files.RaceSettingData +import dev.nikomaru.raceassist.data.files.RaceUtils import dev.nikomaru.raceassist.utils.Lang -import dev.nikomaru.raceassist.utils.Utils import dev.nikomaru.raceassist.utils.Utils.locale import org.bukkit.Bukkit import org.bukkit.command.CommandSender @@ -28,13 +28,13 @@ import org.bukkit.command.CommandSender @CommandMethod("ra|RaceAssist player") class PlayerRemoveCommand { @CommandPermission("raceassist.commands.player.remove") - @CommandMethod("remove ") + @CommandMethod("remove ") suspend fun removePlayer(sender: CommandSender, - @Argument(value = "raceId", suggestions = "raceId") raceId: String, + @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String, @Argument(value = "playerName", suggestions = "playerName") playerName: String) { val locale = sender.locale() val player = Bukkit.getOfflinePlayerIfCached(playerName) ?: return sender.sendMessage(Lang.getComponent("player-add-not-exist", locale)) - if (Utils.returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return RaceSettingData.removeJockey(raceId, player) sender.sendMessage(Lang.getComponent("to-delete-player-from-race-group", locale, raceId)) diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/player/PlayerReplacementCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/player/PlayerReplacementCommand.kt index d19784f..d3ad4ce 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/player/PlayerReplacementCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/player/PlayerReplacementCommand.kt @@ -19,8 +19,8 @@ package dev.nikomaru.raceassist.race.commands.player import cloud.commandframework.annotations.* import dev.nikomaru.raceassist.data.files.RaceSettingData +import dev.nikomaru.raceassist.data.files.RaceUtils import dev.nikomaru.raceassist.utils.Lang -import dev.nikomaru.raceassist.utils.Utils import dev.nikomaru.raceassist.utils.Utils.locale import org.bukkit.Bukkit import org.bukkit.command.CommandSender @@ -29,26 +29,26 @@ import org.bukkit.command.CommandSender class PlayerReplacementCommand { @CommandPermission("raceassist.commands.player.replacement") - @CommandMethod("replacement set ") + @CommandMethod("replacement set ") suspend fun setReplacement(sender: CommandSender, - @Argument(value = "raceId", suggestions = "raceId") raceId: String, + @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String, @Argument(value = "playerName", suggestions = "playerName") playerName: String, @Argument(value = "replacement") replacement: String) { val locale = sender.locale() val player = Bukkit.getOfflinePlayerIfCached(playerName) ?: return sender.sendMessage(Lang.getComponent("player-add-not-exist", locale)) - if (Utils.returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return RaceSettingData.setReplacement(raceId, player.uniqueId, replacement) sender.sendMessage(Lang.getComponent("player-set-replacement", locale)) } @CommandPermission("raceassist.commands.player.replacement") - @CommandMethod("replacement remove ") + @CommandMethod("replacement remove ") suspend fun removeReplacement(sender: CommandSender, - @Argument(value = "raceId", suggestions = "raceId") raceId: String, + @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String, @Argument(value = "playerName", suggestions = "playerName") playerName: String) { val locale = sender.locale() val player = Bukkit.getOfflinePlayerIfCached(playerName) ?: return sender.sendMessage(Lang.getComponent("player-add-not-exist", locale)) - if (Utils.returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return RaceSettingData.removeReplacement(raceId, player.uniqueId) sender.sendMessage(Lang.getComponent("player-remove-replacement", locale)) } diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/race/RaceDebugCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/race/RaceDebugCommand.kt index 325c234..698fc48 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/race/RaceDebugCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/race/RaceDebugCommand.kt @@ -20,12 +20,9 @@ package dev.nikomaru.raceassist.race.commands.race import cloud.commandframework.annotations.* import com.github.shynixn.mccoroutine.bukkit.launch import dev.nikomaru.raceassist.RaceAssist.Companion.plugin -import dev.nikomaru.raceassist.data.files.PlaceSettingData +import dev.nikomaru.raceassist.data.files.* import dev.nikomaru.raceassist.utils.Lang -import dev.nikomaru.raceassist.utils.Utils import dev.nikomaru.raceassist.utils.Utils.displayLap -import dev.nikomaru.raceassist.utils.Utils.getCentralPoint -import dev.nikomaru.raceassist.utils.Utils.getPolygon import dev.nikomaru.raceassist.utils.Utils.getRaceDegree import dev.nikomaru.raceassist.utils.Utils.judgeLap import dev.nikomaru.raceassist.utils.Utils.stop @@ -46,34 +43,35 @@ import kotlin.math.roundToInt class RaceDebugCommand { @CommandPermission("raceassist.commands.race.debug") - @CommandMethod("debug ") - suspend fun debug(sender: CommandSender, @Argument(value = "raceId", suggestions = "raceId") raceId: String) { + @CommandMethod("debug ") + suspend fun debug(sender: CommandSender, @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String) { if (sender !is Player) { sender.sendMessage("Only the player can do this.") return } val locale = sender.locale() plugin.launch { - if (Utils.returnCanRaceSetting(raceId, sender)) return@launch - if (!getCircuitExist(raceId)) { + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return@launch + val placeId = RaceSettingData.getPlaceId(raceId) + if (!getCircuitExist(placeId)) { sender.sendMessage(Lang.getComponent("no-exist-race", locale)) return@launch } - val insidePolygon = getPolygon(raceId, true) - val outsidePolygon = getPolygon(raceId, false) + val insidePolygon = PlaceSettingData.getInsidePolygon(placeId) + val outsidePolygon = PlaceSettingData.getOutsidePolygon(placeId) if (insidePolygon.npoints < 3 || outsidePolygon.npoints < 3) { sender.sendMessage(Lang.getComponent("no-exist-race", locale)) return@launch } - val reverse = PlaceSettingData.getReverse(raceId) - val lap: Int = PlaceSettingData.getLap(raceId) + val reverse = PlaceSettingData.getReverse(placeId) + val lap: Int = RaceSettingData.getLap(raceId) val threshold = 40 val centralXPoint: Int = - getCentralPoint(raceId, true) ?: return@launch sender.sendMessage(Lang.getComponent("no-exist-central-point", locale)) + PlaceSettingData.getCentralXPoint(placeId) ?: return@launch sender.sendMessage(Lang.getComponent("no-exist-central-point", locale)) val centralYPoint: Int = - getCentralPoint(raceId, false) ?: return@launch sender.sendMessage(Lang.getComponent("no-exist-central-point", locale)) - val goalDegree: Int = PlaceSettingData.getGoalDegree(raceId) + PlaceSettingData.getCentralYPoint(placeId) ?: return@launch sender.sendMessage(Lang.getComponent("no-exist-central-point", locale)) + val goalDegree: Int = PlaceSettingData.getGoalDegree(placeId) var beforeDegree = 0 var currentLap = 0 var counter = 0 @@ -81,11 +79,8 @@ class RaceDebugCommand { var totalDegree = 0 val lengthCircle = calcurateLength(insidePolygon) - - - for (timer in 0..4) { - val showTimer = async(minecraft) { + val showTimer = async(Dispatchers.minecraft) { sender.showTitle(Title.title(Lang.getComponent("${5 - timer}", locale), Lang.getComponent("", locale))) } delay(1000) @@ -166,8 +161,8 @@ class RaceDebugCommand { return total } - private suspend fun getCircuitExist(raceId: String) = newSuspendedTransaction(Dispatchers.IO) { - (PlaceSettingData.getInsidePolygon(raceId).npoints > 0 && PlaceSettingData.getInsidePolygon(raceId).npoints > 0) + private suspend fun getCircuitExist(placeId: String) = newSuspendedTransaction(Dispatchers.IO) { + (PlaceSettingData.getInsidePolygon(placeId).npoints > 0 && PlaceSettingData.getInsidePolygon(placeId).npoints > 0) } } \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/race/RaceHorseCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/race/RaceHorseCommand.kt new file mode 100644 index 0000000..74ac605 --- /dev/null +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/race/RaceHorseCommand.kt @@ -0,0 +1,97 @@ +/* + * Copyright © 2021-2022 Nikomaru + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package dev.nikomaru.raceassist.race.commands.race + +import cloud.commandframework.annotations.* +import dev.nikomaru.raceassist.data.files.RaceSettingData +import dev.nikomaru.raceassist.data.files.RaceUtils +import dev.nikomaru.raceassist.utils.Lang +import dev.nikomaru.raceassist.utils.Utils.locale +import dev.nikomaru.raceassist.utils.Utils.toLivingHorse +import dev.nikomaru.raceassist.utils.Utils.toOfflinePlayer +import org.bukkit.Bukkit +import org.bukkit.command.CommandSender +import org.bukkit.entity.Horse +import org.bukkit.entity.Player +import java.util.* + +@CommandMethod("ra|RaceAssist race horse") +@CommandPermission("raceassist.commands.race.horse") +class RaceHorseCommand { + + @CommandMethod("add ") + suspend fun addHorse(sender: CommandSender, @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String) { + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return + if (sender !is Player) { + sender.sendMessage("Only the player can do this.") + return + } + val horse = sender.getTargetEntity(10) + if (horse == null) { + sender.sendMessage("There is no entity within 10 blocks.") + return + } else if (horse !is Horse) { + sender.sendMessage("The entity is not a player.") + return + } + val owner = horse.owner ?: return sender.sendMessage("The horse is not owned.") + + RaceSettingData.setHorse(raceId, owner.uniqueId, horse.uniqueId) + sender.sendRichMessage(" $raceId に ${owner.name} の馬を追加しました。") + } + + @CommandMethod("remove ") + suspend fun removeHorse(sender: CommandSender, + @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String, + @Argument(value = "playerName", suggestions = "playerName") playerName: String) { + val locale = sender.locale() + val player = Bukkit.getOfflinePlayerIfCached(playerName) ?: return sender.sendMessage(Lang.getComponent("player-add-not-exist", locale)) + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return + RaceSettingData.removeHorse(raceId, player.uniqueId) + sender.sendRichMessage(" $raceId から ${player.name} の馬を削除しました。") + } + + @CommandMethod("list ") + suspend fun listHorse(sender: CommandSender, @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String) { + val locale = sender.locale() + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return + val horses = RaceSettingData.getHorse(raceId) + if (horses.isEmpty()) { + sender.sendRichMessage(" $raceId に馬が登録されていません。") + return + } + sender.sendRichMessage(" $raceId の馬一覧") + horses.forEach { (player, horse) -> + val livingHorse = horse.toLivingHorse() + if (livingHorse == null) { + sender.sendRichMessage(" ${player.toOfflinePlayer().name} の馬は存在しません。") + } else { + sender.sendRichMessage(" ${player.toOfflinePlayer().name} : ${livingHorse.name}") + } + } + } + + @CommandMethod("delete ") + suspend fun deleteHorse(sender: CommandSender, @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String) { + val locale = sender.locale() + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return + RaceSettingData.deleteHorse(raceId) + sender.sendRichMessage(" $raceId の馬を全て削除しました。") + } + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/race/RaceStartCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/race/RaceStartCommand.kt index 7cb483f..5abdf8c 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/race/RaceStartCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/race/RaceStartCommand.kt @@ -18,479 +18,33 @@ package dev.nikomaru.raceassist.race.commands.race import cloud.commandframework.annotations.* -import com.github.shynixn.mccoroutine.bukkit.launch -import dev.nikomaru.raceassist.RaceAssist.Companion.plugin -import dev.nikomaru.raceassist.bet.BetUtils -import dev.nikomaru.raceassist.data.files.* -import dev.nikomaru.raceassist.files.Config -import dev.nikomaru.raceassist.race.* -import dev.nikomaru.raceassist.utils.* -import dev.nikomaru.raceassist.utils.Lang.mm -import dev.nikomaru.raceassist.utils.Utils.displayLap -import dev.nikomaru.raceassist.utils.Utils.getRaceDegree -import dev.nikomaru.raceassist.utils.Utils.judgeLap -import dev.nikomaru.raceassist.utils.Utils.locale -import dev.nikomaru.raceassist.utils.Utils.stop -import dev.nikomaru.raceassist.utils.coroutines.minecraft -import kotlinx.coroutines.* -import kotlinx.datetime.* -import kotlinx.datetime.TimeZone -import kotlinx.serialization.encodeToString -import net.kyori.adventure.text.Component.text -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer -import net.kyori.adventure.title.Title -import okhttp3.* -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.RequestBody.Companion.toRequestBody -import org.apache.commons.lang.RandomStringUtils -import org.bukkit.Bukkit -import org.bukkit.OfflinePlayer +import dev.nikomaru.raceassist.data.files.RaceUtils +import dev.nikomaru.raceassist.race.RaceJudgement import org.bukkit.command.CommandSender -import org.bukkit.entity.Horse -import org.bukkit.entity.Player -import org.bukkit.scoreboard.* -import org.json.simple.JSONArray -import org.json.simple.JSONObject -import java.awt.Polygon -import java.io.OutputStream -import java.net.HttpURLConnection -import java.net.URL -import java.util.* -import javax.net.ssl.HttpsURLConnection -import kotlin.math.* @CommandMethod("ra|RaceAssist race") class RaceStartCommand { @CommandPermission("raceassist.commands.race.start") - @CommandMethod("start ") - suspend fun start(sender: CommandSender, @Argument(value = "raceId", suggestions = "raceId") raceId: String) { - val locale = sender.locale() - - if (PlaceSettingData.getInsidePolygon(raceId).npoints == 0 || PlaceSettingData.getOutsidePolygon(raceId).npoints == 0) { - sender.sendMessage(Lang.getComponent("no-exist-race", locale)) - return - } - val jockeys: ArrayList = ArrayList() - RaceSettingData.getJockeys(raceId).forEach { - if (it.isOnline) { - jockeys.add(it as Player) - sender.sendMessage(Lang.getComponent("player-join", locale, it.name)) - } else { - sender.sendMessage(Lang.getComponent("player-is-offline", locale, it.name)) - } - } - if (jockeys.size < 2) { - sender.sendMessage(Lang.getComponent("over-two-users-need", locale)) + @CommandMethod("start ") + suspend fun start( + sender: CommandSender, @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String, + ) { + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return + val raceJudgement = RaceJudgement(raceId, sender) + if (!raceJudgement.setting()) { return } - - val centralXPoint: Int = - PlaceSettingData.getCentralXPoint(raceId) ?: return sender.sendMessage(Lang.getComponent("no-exist-central-point", locale)) - val centralYPoint: Int = - PlaceSettingData.getCentralYPoint(raceId) ?: return sender.sendMessage(Lang.getComponent("no-exist-central-point", locale)) - val goalDegree: Int = PlaceSettingData.getGoalDegree(raceId) - - val jockeyCount = jockeys.size - val finishJockeys: ArrayList = ArrayList() - val totalDegree: HashMap = HashMap() - val beforeDegree: HashMap = HashMap() - val currentLap: HashMap = HashMap() - val threshold = Config.config.threshold - val raceAudience: TreeSet = TreeSet() - var suspend = false - - val passBorders: HashMap = HashMap() - val time: HashMap = HashMap() - - val insidePolygon = PlaceSettingData.getInsidePolygon(raceId) - - //観客(スコアボードを表示する人)の設定 - - val audiences = RaceAudience() - - Utils.audience[raceId]?.forEach { - audiences.add(Bukkit.getOfflinePlayer(it)) - } - jockeys.forEach { - audiences.add(it) - } - if (sender is Player) { - audiences.add(sender) - } - audiences.getUUID().forEach { - raceAudience.add(it) - } - - //5.4.3...1 のカウント - var timer1 = 0 - while (timer1 <= 4) { - audiences.showTitle(Title.title(text("${5 - timer1}"), text(""))) - delay(1000) - timer1++ - } - - val lap = PlaceSettingData.getLap(raceId) - val outsidePolygon = PlaceSettingData.getOutsidePolygon(raceId) - val reverse = PlaceSettingData.getReverse(raceId) - val innerCircumference = getInnerCircumference(insidePolygon) - - jockeys.forEach { - beforeDegree[it.uniqueId] = getRaceDegree(if (!reverse) (it.location.blockX - centralXPoint).toDouble() - else (-1 * (it.location.blockX - centralXPoint)).toDouble(), (it.location.blockZ - centralYPoint).toDouble()) - currentLap[it.uniqueId] = 0 - passBorders[it.uniqueId] = 0 - } - - val beforeTime = System.currentTimeMillis() - audiences.showTitleI18n("race-start") - - //最初の位置をランダムに選んだ人から決める - //最初の人から相対的に距離を決めるがゴールをまたいでいた場合後方にいる人がマイナスにされない - val randomJockey = jockeys.random() - val startDegree = getStartPoint(randomJockey, centralYPoint, reverse, centralXPoint) - - // 結果の保存のため - val limit = ((Config.config.resultTimeOut) * (1000.0 / (Config.config.delay).toDouble())).toLong() * 1000 - val goalDistance = getGoalDistance(lap, goalDegree, startDegree, innerCircumference) - - val senderName = if (sender is Player) sender.name else "Console" - - val uuidToName = jockeys.associate { it.uniqueId to it.name } as HashMap - - val rectangleData = RectangleData(outsidePolygon.bounds2D.maxX.roundToInt(), - outsidePolygon.bounds2D.maxY.roundToInt(), - outsidePolygon.bounds2D.minX.roundToInt(), - outsidePolygon.bounds2D.minY.roundToInt()) - val horses: HashMap = HashMap() - jockeys.forEach { - val vehicle = it.vehicle - if (vehicle != null) { - if (vehicle is Horse) { - horses[it.uniqueId] = vehicle.uniqueId - } - } - } - - val raceUniqueId = RandomStringUtils.randomAlphanumeric(15) - val raceResultData = RaceResultData("1.0", - raceId, - raceUniqueId, - senderName, - horses, - Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()), - Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()), - false, - hashMapOf(), - lap, - goalDistance, - uuidToName, - RaceSettingData.getReplacement(raceId), - rectangleData, - insidePolygon, - outsidePolygon, - arrayListOf()) - - //レースの処理 - while (true) { - //正常時の終了 - val currentRacaData = CurrentRacaData(((System.currentTimeMillis() - beforeTime).toDouble() / 1000), arrayListOf()) - - if (jockeys.size < 1) { - BetUtils.returnBet(Bukkit.getOfflinePlayer(finishJockeys[0]), raceId, sender, locale) - break - } - //stopコマンドによる終了 - if (stop[raceId] == true) { - suspend = true - raceResultData.suspend = true - audiences.sendMessageI18n("suspended-race-by-operator") - break - } - if ((System.currentTimeMillis() - beforeTime) > limit) { - suspend = true - raceResultData.suspend = true - audiences.sendMessageI18n("suspended-race-by-limit") - break - } - val iterator = jockeys.iterator() - - while (iterator.hasNext()) { - val player: Player = iterator.next() - - if (!player.isOnline) { - iterator.remove() - continue - } - //各騎手の位置の取得 - val nowX: Int = player.location.blockX - val nowY = player.location.blockZ - val relativeNowX = if (!reverse) nowX - centralXPoint else -1 * (nowX - centralXPoint) - val relativeNowY = nowY - centralYPoint - val currentDegree = getRaceDegree(relativeNowY.toDouble(), relativeNowX.toDouble()) - val uuid = player.uniqueId - val beforeLap = currentLap[uuid] - - //ラップの計算 - withContext(Dispatchers.Default) { - - currentLap[uuid] = currentLap[uuid]!! + judgeLap(goalDegree, beforeDegree[uuid], currentDegree, threshold) - passBorders[uuid] = passBorders[uuid]!! + judgeLap(0, beforeDegree[uuid], currentDegree, threshold) - displayLap(currentLap[uuid], beforeLap, player, lap) - beforeDegree[uuid] = currentDegree - totalDegree[uuid] = currentDegree + (passBorders[uuid]!! * 360) - } - - val currentDistance = (((totalDegree[uuid]!!.toDouble() - startDegree.toDouble()) / 360.0) * innerCircumference).toInt() - - val currentResultData = PlayerRaceData(uuid, false, currentDistance, nowX, nowY) - currentRacaData.playerRaceData.add(currentResultData) - - //コース内にいるか判断 - if (insidePolygon.contains(nowX, nowY) || !outsidePolygon.contains(nowX, nowY)) { - player.sendActionBar(Lang.getComponent("outside-the-racetrack", player.locale())) - } - - //ゴールした時の処理 - if (currentLap[uuid]!! >= lap) { - withContext(Dispatchers.Default) { - iterator.remove() - finishJockeys.add(uuid) - totalDegree.remove(uuid) - currentLap.remove(uuid) - player.showTitle(Title.title(Lang.getComponent("player-ranking", player.locale(), jockeyCount - jockeys.size, jockeyCount), - text(""))) - } - time[uuid] = ((System.currentTimeMillis() - beforeTime) / 1000).toInt() - continue - } - - } - - - finishJockeys.forEach { finishJockey -> - val player = Bukkit.getPlayer(finishJockey) ?: return@forEach - - val uuid = player.uniqueId - - val none = currentRacaData.playerRaceData.none { it.uuid == uuid } - if (none) { - currentRacaData.playerRaceData.add(PlayerRaceData(uuid, true, null, null, null)) - } - } - - //順位の表示 - plugin.launch { - val displayRanking = async(minecraft) { - displayScoreboard(finishJockeys.plus(decideRanking(totalDegree)), - totalDegree, - raceAudience, - innerCircumference.roundToInt(), - startDegree, - goalDegree, - lap, - raceId) - } - delay(Config.config.delay) - displayRanking.await() - }.join() - //現在のレースの状況保存のため - raceResultData.currentRaceData.add(currentRacaData) - } - - //終了時の処理 - raceResultData.finish = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) - audiences.showTitleI18n("finish-race") - delay(2000) - - for (i in 0 until finishJockeys.size) { - audiences.sendMessageI18n("to-notice-ranking-message", i + 1, Bukkit.getPlayer(finishJockeys[i])?.name!!) - } - - - finishJockeys.forEachIndexed { index, element -> - raceResultData.result[index + 1] = element - } - - //結果の保存 - putRaceResult(raceResultData) - sendWebHook(finishJockeys, time, RaceSettingData.getOwner(raceId), raceId, suspend) - - //後始末 - Bukkit.getOnlinePlayers().forEach { - it.scoreboard.clearSlot(DisplaySlot.SIDEBAR) + raceJudgement.start() + while (raceJudgement.finished) { + raceJudgement.calculate() + raceJudgement.display() } - - } - - private fun getStartPoint(randomJockey: Player, centralYPoint: Int, reverse: Boolean, centralXPoint: Int) = - getRaceDegree((randomJockey.location.blockZ - centralYPoint).toDouble(), if (reverse) { - (-1 * (randomJockey.location.blockX - centralXPoint)).toDouble() - } else { - (randomJockey.location.blockX - centralXPoint).toDouble() - }) - - private suspend fun putRaceResult(raceResultData: RaceResultData) { - withContext(Dispatchers.IO) { - val resultFolder = plugin.dataFolder.resolve("result") - resultFolder.mkdirs() - val resultFile = resultFolder.resolve("${raceResultData.raceUniqueId}.json") - resultFile.writeText(json.encodeToString(raceResultData)) - sendResultWebHook(raceResultData) + raceJudgement.last() + if (!raceJudgement.suspend) { + raceJudgement.payDividend() } } - private fun sendResultWebHook(raceResultData: RaceResultData) { - val json = json.encodeToString(raceResultData) - val body: RequestBody = json.toRequestBody("application/json; charset=utf-8".toMediaType()) - val client = OkHttpClient() - Config.config.resultWebhook.forEach { - val request: Request = - Request.Builder().url(it.url + raceResultData.raceUniqueId).header("Authorization", Credentials.basic(it.name, it.password)) - .post(body).build() - client.newCall(request).execute().body?.close() - } - } - - private suspend fun getInnerCircumference(insidePolygon: Polygon) = withContext(Dispatchers.Default) { - //内周の距離のカウント - var total = 0.0 - val insideX = insidePolygon.xpoints - val insideY = insidePolygon.ypoints - for (i in 0 until insidePolygon.npoints) { - total += if (i <= insidePolygon.npoints - 2) { - hypot((insideX[i] - insideX[i + 1]).toDouble(), (insideY[i] - insideY[i + 1]).toDouble()) - } else { - hypot((insideX[i] - insideX[0]).toDouble(), (insideY[i] - insideY[0]).toDouble()) - } - } - total - } - - private suspend fun sendWebHook(finishJockey: ArrayList, - time: HashMap, - starter: OfflinePlayer, - raceId: String, - suspend: Boolean) { - val json = JSONObject() - json["username"] = "RaceAssist" - json["avatar_url"] = "https://3.bp.blogspot.com/-Y3AVYVjLcPs/UYiNxIliDxI/AAAAAAAARSg/nZLIqBRUta8/s800/animal_uma.png" - val result = JSONArray() - val embeds = JSONArray() - val author = JSONObject() - val embedsObject = JSONObject() - embedsObject["title"] = if (suspend) "RaceAssist_suspend" else "RaceAssist" - author["name"] = Lang.getText("discord-webhook-name", Locale.getDefault(), starter.name, raceId) - author["icon_url"] = "https://crafthead.net/avatar/$starter" - embedsObject["author"] = author - for (i in 0 until finishJockey.size) { - val playerResult = JSONObject() - playerResult["name"] = Lang.getText("discord-webhook-ranking", Locale.getDefault(), i + 1) - playerResult["value"] = String.format("%s %2d:%02d", - Bukkit.getPlayer(finishJockey[i])?.name, - floor((time[finishJockey[i]]!!.toDouble() / 60)).toInt(), - time[finishJockey[i]]!! % 60) - playerResult["inline"] = true - result.add(playerResult) - } - embedsObject["fields"] = result - embeds.add(embedsObject) - json["embeds"] = embeds - - sendDiscordResultWebHook(json.toString()) - } - - private suspend fun sendDiscordResultWebHook(json: String) = withContext(Dispatchers.IO) { - - Config.config.discordWebHook.result.forEach { - try { - val webHookUrl = URL(it) - val con: HttpsURLConnection = (webHookUrl.openConnection() as HttpsURLConnection) - - con.addRequestProperty("Content-Type", "application/JSON; charset=utf-8") - con.addRequestProperty("User-Agent", "DiscordBot") - con.doOutput = true - con.requestMethod = "POST" - - con.setRequestProperty("Content-Length", json.length.toString()) - - val stream: OutputStream = con.outputStream - stream.write(json.toByteArray(Charsets.UTF_8)) - stream.flush() - stream.close() - - val status: Int = con.responseCode - if (status != HttpURLConnection.HTTP_OK && status != HttpURLConnection.HTTP_NO_CONTENT) { - plugin.logger.warning("error:$status") - } - con.disconnect() - - } catch (e: Exception) { - e.printStackTrace() - } - } - } - - private suspend fun displayScoreboard(nowRankings: List, - currentDegree: HashMap, - raceAudience: TreeSet, - innerCircumference: Int, - startDegree: Int, - goalDegree: Int, - lap: Int, - raceId: String) { - - raceAudience.forEach { - - if (Bukkit.getOfflinePlayer(it).isOnline) { - val player = Bukkit.getPlayer(it)!! - val manager: ScoreboardManager = Bukkit.getScoreboardManager() - val scoreboard = manager.newScoreboard - val objective: Objective = scoreboard.registerNewObjective(Lang.getText("scoreboard-ranking", player.locale()), - "dummy", - Lang.getComponent("scoreboard-now-ranking", player.locale())) - objective.displaySlot = DisplaySlot.SIDEBAR - - val goalDistance = getGoalDistance(lap, goalDegree, startDegree, innerCircumference.toDouble()) - - for (i in nowRankings.indices) { - - val playerName = RaceSettingData.getReplacement(raceId)[nowRankings[i]] ?: Bukkit.getPlayer(nowRankings[i])?.name - - val component = if (currentDegree[Bukkit.getPlayer(nowRankings[i])!!.uniqueId] == null) { - Lang.getComponent("scoreboard-now-ranking-and-name", player.locale(), i + 1, playerName) - .append(Lang.getComponent("finished-the-race", player.locale())) - } else { - val currentDistance = - ((currentDegree[Bukkit.getPlayer(nowRankings[i])!!.uniqueId]!!.toDouble() - startDegree.toDouble()) / 360.0 * innerCircumference.toDouble()).toInt() - - Lang.getComponent("scoreboard-now-ranking-and-name", player.locale(), i + 1, playerName) - .append(mm.deserialize("${currentDistance}m/${goalDistance}m ")) - } - - val displayDegree = objective.getScore(LegacyComponentSerializer.legacySection().serialize(component)) - displayDegree.score = nowRankings.size - i - } - player.scoreboard = scoreboard - } - } - } - - private fun getGoalDistance(lap: Int, goalDegree: Int, startDegree: Int, innerCircumference: Double) = - (((lap - 1).toDouble() + if (goalDegree > startDegree) { - ((goalDegree.toDouble() - startDegree.toDouble()) / 360.0) - } else { - ((goalDegree.toDouble() + 360.0 - startDegree.toDouble()) / 360.0) - }) * innerCircumference).toInt() - - private fun decideRanking(totalDegree: HashMap): ArrayList { - val ranking = ArrayList() - val sorted = totalDegree.toList().sortedBy { (_, value) -> value } - sorted.forEach { - ranking.add(it.first) - } - ranking.reverse() - return ranking - } } diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/race/RaceStopCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/race/RaceStopCommand.kt index d9dd7bc..b64749f 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/race/RaceStopCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/race/RaceStopCommand.kt @@ -18,7 +18,7 @@ package dev.nikomaru.raceassist.race.commands.race import cloud.commandframework.annotations.* -import dev.nikomaru.raceassist.utils.Utils +import dev.nikomaru.raceassist.data.files.RaceUtils import dev.nikomaru.raceassist.utils.Utils.stop import kotlinx.coroutines.delay import org.bukkit.command.CommandSender @@ -26,9 +26,9 @@ import org.bukkit.command.CommandSender @CommandMethod("ra|RaceAssist race") class RaceStopCommand { @CommandPermission("raceassist.commands.race.stop") - @CommandMethod("stop ") - suspend fun stop(sender: CommandSender, @Argument(value = "raceId", suggestions = "raceId") raceId: String) { - if (Utils.returnCanRaceSetting(raceId, sender)) return + @CommandMethod("stop ") + suspend fun stop(sender: CommandSender, @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String) { + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return stop[raceId] = true delay(1000) stop[raceId] = false diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingCopyCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingCopyCommand.kt index 51fc7f7..359727d 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingCopyCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingCopyCommand.kt @@ -29,8 +29,8 @@ class SettingCopyCommand { @CommandPermission("raceassist.commands.setting.copy") @CommandMethod("copy ") suspend fun copy(sender: CommandSender, - @Regex(value = "[a-zA-Z]+-\\d+$") @Argument(value = "raceId_1", suggestions = "raceId") raceId_1: String, - @Regex(value = "[a-zA-Z]+-\\d+$") @Argument(value = "raceId_2") raceId_2: String) { + @Regex(value = "[a-zA-Z]+-\\d+$") @Argument(value = "raceId_1") raceId_1: String, + @Regex(value = "[a-zA-Z]+-\\d+$") @Argument(value = "raceId_2", suggestions = "raceId") raceId_2: String) { if (sender !is Player) { sender.sendMessage("Only the player can do this.") return diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingCreateCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingCreateCommand.kt index ba84efd..19ad0d1 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingCreateCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingCreateCommand.kt @@ -18,6 +18,7 @@ package dev.nikomaru.raceassist.race.commands.setting import cloud.commandframework.annotations.* +import dev.nikomaru.raceassist.data.files.PlaceSettingData import dev.nikomaru.raceassist.data.files.RaceSettingData import dev.nikomaru.raceassist.utils.Lang import org.bukkit.command.CommandSender @@ -26,8 +27,10 @@ import org.bukkit.entity.Player @CommandMethod("ra|RaceAssist setting") class SettingCreateCommand { @CommandPermission("raceassist.commands.setting.create") - @CommandMethod("create ") - suspend fun create(sender: CommandSender, @Argument(value = "raceId") @Regex(value = "[^_]+_\\d+$") raceId: String) { + @CommandMethod("create ") + suspend fun create(sender: CommandSender, + @Argument(value = "raceId") @Regex(value = "[^_]+_\\d+$") raceId: String, + @Argument(value = "placeId", suggestions = "placeId") placeId: String) { if (sender !is Player) { sender.sendMessage("Only the player can do this.") return @@ -36,7 +39,11 @@ class SettingCreateCommand { sender.sendMessage(Lang.getComponent("already-used-the-name-race", sender.locale())) return } - RaceSettingData.createRace(raceId, sender) + if (!PlaceSettingData.existsPlace(placeId)) { + sender.sendMessage(Lang.getComponent("not-exists-place", sender.locale())) + return + } + RaceSettingData.createRace(raceId, placeId, sender) sender.sendMessage(Lang.getComponent("to-create-race", sender.locale())) } diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingDeleteCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingDeleteCommand.kt index 3995c53..212f5bf 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingDeleteCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingDeleteCommand.kt @@ -19,21 +19,21 @@ package dev.nikomaru.raceassist.race.commands.setting import cloud.commandframework.annotations.* import dev.nikomaru.raceassist.data.files.RaceSettingData -import dev.nikomaru.raceassist.utils.Utils +import dev.nikomaru.raceassist.data.files.RaceUtils import org.bukkit.command.CommandSender import org.bukkit.entity.Player @CommandMethod("ra|RaceAssist setting") class SettingDeleteCommand { @CommandPermission("raceassist.commands.setting.delete") - @CommandMethod("delete ") + @CommandMethod("delete ") @Confirmation - suspend fun delete(sender: CommandSender, @Argument(value = "raceId", suggestions = "raceId") raceId: String) { + suspend fun delete(sender: CommandSender, @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String) { if (sender !is Player) { sender.sendMessage("Only the player can do this.") return } - if (Utils.returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return RaceSettingData.deleteRace(raceId) } diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceLapCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingLapCommand.kt similarity index 74% rename from src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceLapCommand.kt rename to src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingLapCommand.kt index 460fee3..e82e690 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/place/PlaceLapCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingLapCommand.kt @@ -15,35 +15,34 @@ * along with this program. If not, see . */ -package dev.nikomaru.raceassist.race.commands.place +package dev.nikomaru.raceassist.race.commands.setting import cloud.commandframework.annotations.* import cloud.commandframework.annotations.specifier.Range -import dev.nikomaru.raceassist.data.files.PlaceSettingData +import dev.nikomaru.raceassist.data.files.* import dev.nikomaru.raceassist.utils.Lang -import dev.nikomaru.raceassist.utils.Utils import org.bukkit.command.CommandSender import org.bukkit.entity.Player -@CommandMethod("ra|RaceAssist place") -class PlaceLapCommand { - @CommandPermission("raceassist.commands.place.lap") - @CommandMethod("lap ") +@CommandMethod("ra|RaceAssist setting") +class SettingLapCommand { + @CommandPermission("raceassist.commands.setting.lap") + @CommandMethod("lap ") suspend fun setLap(sender: CommandSender, - @Argument(value = "raceId", suggestions = "raceId") raceId: String, + @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String, @Argument(value = "lap") @Range(min = "1", max = "100") lap: Int) { if (sender !is Player) { sender.sendMessage("Only the player can do this.") return } - if (Utils.returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return if (lap < 1) { sender.sendMessage(Lang.getComponent("to-need-enter-over-1", sender.locale())) return } - PlaceSettingData.setLap(raceId, lap) + RaceSettingData.setLap(raceId, lap) sender.sendMessage(Lang.getComponent("to-set-lap", sender.locale())) } diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingPlaceIdCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingPlaceIdCommand.kt new file mode 100644 index 0000000..eaae31a --- /dev/null +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingPlaceIdCommand.kt @@ -0,0 +1,37 @@ +/* + * Copyright © 2021-2022 Nikomaru + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package dev.nikomaru.raceassist.race.commands.setting + +import cloud.commandframework.annotations.* +import dev.nikomaru.raceassist.data.files.* +import org.bukkit.command.CommandSender + +@CommandMethod("ra|RaceAssist setting") +class SettingPlaceIdCommand { + + @CommandPermission("raceassist.commands.setting.placeId") + @CommandMethod("placeId ") + suspend fun setPlaceId(sender: CommandSender, + @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String, + @Argument(value = "placeId", suggestions = "placeId") placeId: String) { + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return + RaceSettingData.setPlaceId(raceId, placeId) + sender.sendRichMessage("$raceId の placeId を $placeId に設定しました。") + } + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingReplacemcntCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingReplacemcntCommand.kt index 0c0497e..29f8287 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingReplacemcntCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingReplacemcntCommand.kt @@ -19,8 +19,8 @@ package dev.nikomaru.raceassist.race.commands.setting import cloud.commandframework.annotations.* import dev.nikomaru.raceassist.data.files.RaceSettingData +import dev.nikomaru.raceassist.data.files.RaceUtils import dev.nikomaru.raceassist.utils.Lang -import dev.nikomaru.raceassist.utils.Utils import dev.nikomaru.raceassist.utils.Utils.locale import org.bukkit.Bukkit import org.bukkit.command.CommandSender @@ -29,12 +29,12 @@ import org.bukkit.command.CommandSender @CommandPermission("raceassist.commands.setting.replacement") class SettingReplacemcntCommand { - @CommandMethod("replacement set ") + @CommandMethod("replacement set ") suspend fun add(sender: CommandSender, - @Argument(value = "raceId", suggestions = "raceId") raceId: String, + @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String, @Argument(value = "playerName", suggestions = "playerName") playerName: String, @Argument(value = "replacement") replacement: String) { - if (Utils.returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return val locale = sender.locale() val player = Bukkit.getOfflinePlayerIfCached(playerName) ?: return sender.sendMessage(Lang.getComponent("player-add-not-exist", locale)) @@ -43,11 +43,11 @@ class SettingReplacemcntCommand { } - @CommandMethod("replacement remove ") + @CommandMethod("replacement remove ") suspend fun remove(sender: CommandSender, - @Argument(value = "raceId", suggestions = "raceId") raceId: String, + @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String, @Argument(value = "playerName", suggestions = "playerName") playerName: String) { - if (Utils.returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return val locale = sender.locale() val player = Bukkit.getOfflinePlayerIfCached(playerName) ?: return sender.sendMessage(Lang.getComponent("player-add-not-exist", locale)) @@ -55,9 +55,9 @@ class SettingReplacemcntCommand { sender.sendMessage(Lang.getComponent("command-result-replacement-remove-success", locale)) } - @CommandMethod("replacement delete ") - suspend fun delete(sender: CommandSender, @Argument(value = "raceId", suggestions = "raceId") raceId: String) { - if (Utils.returnCanRaceSetting(raceId, sender)) return + @CommandMethod("replacement delete ") + suspend fun delete(sender: CommandSender, @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String) { + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return val locale = sender.locale() RaceSettingData.deleteReplacement(raceId) @@ -65,9 +65,9 @@ class SettingReplacemcntCommand { } - @CommandMethod("replacement list ") - suspend fun list(sender: CommandSender, @Argument(value = "raceId", suggestions = "raceId") raceId: String) { - if (Utils.returnCanRaceSetting(raceId, sender)) return + @CommandMethod("replacement list ") + suspend fun list(sender: CommandSender, @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String) { + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return val locale = sender.locale() val replacement = RaceSettingData.getReplacement(raceId) if (replacement.isEmpty()) { diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingStaffCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingStaffCommand.kt index 992be7f..2537b52 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingStaffCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingStaffCommand.kt @@ -19,9 +19,8 @@ package dev.nikomaru.raceassist.race.commands.setting import cloud.commandframework.annotations.* import dev.nikomaru.raceassist.data.files.RaceSettingData -import dev.nikomaru.raceassist.data.files.StaffSettingData +import dev.nikomaru.raceassist.data.files.RaceUtils import dev.nikomaru.raceassist.utils.Lang -import dev.nikomaru.raceassist.utils.Utils import dev.nikomaru.raceassist.utils.Utils.locale import org.bukkit.Bukkit import org.bukkit.command.CommandSender @@ -30,17 +29,17 @@ import org.bukkit.command.CommandSender @CommandPermission("raceassist.commands.setting.staff") class SettingStaffCommand { - @CommandMethod("staff add ") + @CommandMethod("staff add ") suspend fun addStaff(sender: CommandSender, - @Argument(value = "raceId", suggestions = "raceId") raceId: String, + @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String, @Argument(value = "playerName", suggestions = "playerName") playerName: String) { - if (Utils.returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return val locale = sender.locale() val target = Bukkit.getOfflinePlayerIfCached(playerName) ?: return sender.sendMessage(Lang.getComponent("player-add-not-exist", locale)) - if (StaffSettingData.addStaff(raceId, target)) { + if (RaceSettingData.addStaff(raceId, target)) { sender.sendMessage(Lang.getComponent("add-staff", locale)) } else { sender.sendMessage(Lang.getComponent("already-added-staff", locale)) @@ -48,11 +47,11 @@ class SettingStaffCommand { } - @CommandMethod("staff remove ") + @CommandMethod("staff remove ") suspend fun removeStaff(sender: CommandSender, - @Argument(value = "raceId", suggestions = "raceId") raceId: String, + @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String, @Argument(value = "playerName", suggestions = "playerName") playerName: String) { - if (Utils.returnCanRaceSetting(raceId, sender)) return + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return val locale = sender.locale() val target = Bukkit.getOfflinePlayerIfCached(playerName) ?: return sender.sendMessage(Lang.getComponent("player-add-not-exist", locale)) @@ -60,7 +59,7 @@ class SettingStaffCommand { return sender.sendMessage(Lang.getComponent("cant-remove-yourself-staff", locale)) } - if (StaffSettingData.removeStaff(raceId, target)) { + if (RaceSettingData.removeStaff(raceId, target)) { sender.sendMessage(Lang.getComponent("delete-staff", locale)) } else { sender.sendMessage(Lang.getComponent("not-find-staff", locale)) @@ -68,10 +67,10 @@ class SettingStaffCommand { } - @CommandMethod("staff list ") - suspend fun listStaff(sender: CommandSender, @Argument(value = "raceId", suggestions = "raceId") raceId: String) { - if (Utils.returnCanRaceSetting(raceId, sender)) return - StaffSettingData.getStaffs(raceId).forEach { + @CommandMethod("staff list ") + suspend fun listStaff(sender: CommandSender, @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String) { + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return + RaceSettingData.getStaffs(raceId).forEach { sender.sendMessage(it.name.toString()) } diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingViewCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingViewCommand.kt index 46a4009..7611b85 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingViewCommand.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/commands/setting/SettingViewCommand.kt @@ -27,22 +27,27 @@ import java.util.* @CommandMethod("ra|RaceAssist setting") class SettingViewCommand { - @CommandMethod("view ") + @CommandMethod("view ") @CommandPermission("raceassist.commands.setting.view") - suspend fun view(sender: CommandSender, @Argument(value = "raceId", suggestions = "raceId") raceId: String) { + suspend fun view(sender: CommandSender, @Argument(value = "operateRaceId", suggestions = "operateRaceId") raceId: String) { if (!RaceSettingData.existsRace(raceId)) return + if (!RaceUtils.hasRaceControlPermission(raceId, sender)) return val raceData = RaceUtils.getRaceConfig(raceId) + val placeData = RaceUtils.getPlaceConfig(raceId) sender.sendMessage("raceId = ${raceData.raceId}") sender.sendMessage("raceName = ${raceData.raceName}") sender.sendMessage("owner = ${raceData.owner.name}") sender.sendMessage("staff = ${raceData.staff.joinToString { it.name.toString() }}") sender.sendMessage("jockeys = ${raceData.jockeys.joinToString { it.name.toString() }}") sender.sendMessage("replacement = ${raceData.replacement.map { it.key.toName() to it.value }.toMap()}") - sender.sendMessage("x = ${raceData.place.centralX}") - sender.sendMessage("y = ${raceData.place.centralY}") - sender.sendMessage("lap = ${raceData.place.lap}") - sender.sendMessage("reverce = ${raceData.place.reverse}") - sender.sendMessage("goalDegree = ${raceData.place.goalDegree}") + sender.sendMessage("lap = ${raceData.lap}") + sender.sendMessage("-----place-----") + sender.sendMessage("placeId = ${raceData.placeId}") + sender.sendMessage("x = ${placeData.centralX}") + sender.sendMessage("y = ${placeData.centralY}") + sender.sendMessage("reverce = ${placeData.reverse}") + sender.sendMessage("goalDegree = ${placeData.goalDegree}") + sender.sendMessage("-----bet-----") sender.sendMessage("bet-avaiable = ${raceData.bet.available}") sender.sendMessage("bet-returnPercent = ${raceData.bet.returnPercent}") sender.sendMessage("bet-spreadSheetId = ${raceData.bet.spreadSheetId}") diff --git a/src/main/kotlin/dev/nikomaru/raceassist/race/event/SetCentralPointEvent.kt b/src/main/kotlin/dev/nikomaru/raceassist/race/event/SetCentralPointEvent.kt index cdbcc7b..fd1a6c4 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/race/event/SetCentralPointEvent.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/race/event/SetCentralPointEvent.kt @@ -20,7 +20,7 @@ package dev.nikomaru.raceassist.race.event import dev.nikomaru.raceassist.data.files.PlaceSettingData import dev.nikomaru.raceassist.utils.Lang import dev.nikomaru.raceassist.utils.Utils.canSetCentral -import dev.nikomaru.raceassist.utils.Utils.centralRaceId +import dev.nikomaru.raceassist.utils.Utils.centralPlaceId import org.bukkit.event.EventHandler import org.bukkit.event.Listener import org.bukkit.event.block.Action @@ -36,8 +36,8 @@ class SetCentralPointEvent : Listener { return } - PlaceSettingData.setCentralXPoint(centralRaceId[event.player.uniqueId]!!, event.clickedBlock?.location?.blockX ?: 0) - PlaceSettingData.setCentralYPoint(centralRaceId[event.player.uniqueId]!!, event.clickedBlock?.location?.blockZ ?: 0) + PlaceSettingData.setCentralXPoint(centralPlaceId[event.player.uniqueId]!!, event.clickedBlock?.location?.blockX ?: 0) + PlaceSettingData.setCentralYPoint(centralPlaceId[event.player.uniqueId]!!, event.clickedBlock?.location?.blockZ ?: 0) event.player.sendMessage(Lang.getComponent("to-set-this-point-central", event.player.locale())) canSetCentral.remove(event.player.uniqueId) diff --git a/src/main/kotlin/dev/nikomaru/raceassist/utils/CommandSuggestions.kt b/src/main/kotlin/dev/nikomaru/raceassist/utils/CommandSuggestions.kt index eb01667..5dbe870 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/utils/CommandSuggestions.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/utils/CommandSuggestions.kt @@ -20,6 +20,8 @@ package dev.nikomaru.raceassist.utils import cloud.commandframework.annotations.suggestions.Suggestions import cloud.commandframework.context.CommandContext import dev.nikomaru.raceassist.RaceAssist +import dev.nikomaru.raceassist.data.files.RaceUtils +import kotlinx.coroutines.* import org.bukkit.Bukkit import org.bukkit.command.CommandSender import java.io.File @@ -44,6 +46,49 @@ open class CommandSuggestions { return list } + @Suggestions("placeId") + fun suggestPlaceId(sender: CommandContext, input: String?): List { + val list = ArrayList() + File(RaceAssist.plugin.dataFolder, "PlaceData").listFiles()?.forEach { + list.add(it.nameWithoutExtension) + } + return list + } + + @Suggestions("operateRaceId") + fun suggestOperateRaceId(sender: CommandContext, input: String?): List { + val list = runBlocking { + val raceIds = ArrayList() + File(RaceAssist.plugin.dataFolder, "RaceData").listFiles()?.forEach { + val raceId = it.nameWithoutExtension + if (RaceUtils.hasRaceControlPermission(raceId, sender.sender)) { + raceIds.add(raceId) + } + } + raceIds + } + + return list + } + + @Suggestions("operatePlaceId") + fun suggestOperatePlaceId(sender: CommandContext, input: String?): List { + + val list = runBlocking { + val placeIds = ArrayList() + File(RaceAssist.plugin.dataFolder, "PlaceData").listFiles()?.forEach { + val placeId = it.nameWithoutExtension + if (RaceUtils.hasPlaceControlPermission(placeId, sender.sender)) { + placeIds.add(placeId) + } + } + placeIds + } + + + return list + } + @Suggestions("placeType") fun suggestPlaceType(sender: CommandContext, input: String?): List { return listOf("in", "out") @@ -54,4 +99,6 @@ open class CommandSuggestions { return listOf("on", "off") } -} \ No newline at end of file +} + + diff --git a/src/main/kotlin/dev/nikomaru/raceassist/utils/Lang.kt b/src/main/kotlin/dev/nikomaru/raceassist/utils/Lang.kt index f2c7cf9..90207ad 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/utils/Lang.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/utils/Lang.kt @@ -22,8 +22,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import net.kyori.adventure.text.Component import net.kyori.adventure.text.minimessage.MiniMessage -import java.io.* -import java.nio.file.Files +import java.io.File +import java.io.InputStreamReader import java.text.MessageFormat import java.util.* @@ -49,14 +49,6 @@ object Lang { if (!langDir.exists()) { langDir.mkdir() } - lang.forEach { locale -> - val input: InputStream = this.javaClass.classLoader.getResourceAsStream("lang/$locale.properties") ?: return@forEach - plugin.logger.info("Loading resource lang file for $locale") - val file = File(langDir, "$locale.properties") - if (!file.exists()) { - Files.copy(input, file.toPath()) - } - } withContext(Dispatchers.IO) { langDir.listFiles()?.forEach { plugin.logger.info("Loading local lang file for ${it.nameWithoutExtension}") diff --git a/src/main/kotlin/dev/nikomaru/raceassist/utils/Utils.kt b/src/main/kotlin/dev/nikomaru/raceassist/utils/Utils.kt index e85b655..023ef01 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/utils/Utils.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/utils/Utils.kt @@ -17,19 +17,27 @@ package dev.nikomaru.raceassist.utils -import com.github.shynixn.mccoroutine.bukkit.launch -import dev.nikomaru.raceassist.RaceAssist.Companion.plugin import dev.nikomaru.raceassist.data.files.PlaceSettingData -import dev.nikomaru.raceassist.data.files.RaceSettingData -import dev.nikomaru.raceassist.data.files.StaffSettingData.existStaff +import dev.nikomaru.raceassist.utils.coroutines.async import kotlinx.coroutines.* +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer import net.kyori.adventure.title.Title.title +import okhttp3.OkHttpClient +import org.bukkit.* import org.bukkit.command.CommandSender -import org.bukkit.command.ConsoleCommandSender +import org.bukkit.entity.Horse import org.bukkit.entity.Player import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import java.awt.image.BufferedImage +import java.io.BufferedOutputStream +import java.io.ByteArrayOutputStream +import java.math.BigInteger +import java.security.MessageDigest import java.util.* -import kotlin.math.atan2 +import java.util.concurrent.* +import javax.imageio.ImageIO +import kotlin.math.* object Utils { @@ -38,8 +46,9 @@ object Utils { val canSetOutsideCircuit = HashMap() val circuitRaceId = HashMap() val canSetCentral = HashMap() - val centralRaceId = HashMap() + val centralPlaceId = HashMap() var stop = HashMap() + lateinit var mapColor: Properties suspend fun getInsideRaceExist(raceId: String) = newSuspendedTransaction(Dispatchers.IO) { PlaceSettingData.getInsidePolygon(raceId).npoints > 0 @@ -52,43 +61,60 @@ object Utils { if (currentLap == lap) { return } - plugin.launch { - val count: Long = 2000 - withContext(Dispatchers.Default) { - if (currentLap > beforeLap) { - if (currentLap == lap - 1) { - player.showTitle(title((Lang.getComponent("last-lap", player.locale())), - Lang.getComponent("one-step-forward-lap", player.locale()))) - } else { - player.showTitle(title(Lang.getComponent("now-lap", player.locale(), currentLap, lap), - Lang.getComponent("one-step-forward-lap", player.locale()))) - } - delay(count) - player.clearTitle() - } else if (currentLap < beforeLap) { + val count: Long = 2000 + withContext(Dispatchers.async) { + if (currentLap > beforeLap) { + if (currentLap == lap - 1) { + player.showTitle(title((Lang.getComponent("last-lap", player.locale())), + Lang.getComponent("one-step-forward-lap", player.locale()))) + } else { player.showTitle(title(Lang.getComponent("now-lap", player.locale(), currentLap, lap), - Lang.getComponent("one-step-backwards-lap", player.locale()))) - delay(count) - player.clearTitle() + Lang.getComponent("one-step-forward-lap", player.locale()))) } + delay(count) + player.clearTitle() + } else if (currentLap < beforeLap) { + player.showTitle(title(Lang.getComponent("now-lap", player.locale(), currentLap, lap), + Lang.getComponent("one-step-backwards-lap", player.locale()))) + delay(count) + player.clearTitle() } - }.join() + } } - suspend fun returnCanRaceSetting(raceId: String, player: CommandSender) = withContext(Dispatchers.IO) { - if (player is ConsoleCommandSender) { - return@withContext true + private fun getBlockColor(x: Int, z: Int, world: World): String { + val block = world.getHighestBlockAt(x, z, HeightMap.WORLD_SURFACE) + val key = block.blockData.material.key.asString() + val color = mapColor.getProperty(key) ?: "#000000" + if (color == "#000000") { + println(key) } - (player as Player) - if (!RaceSettingData.existsRace(raceId)) { - player.sendMessage(Lang.getComponent("no-exist-this-raceid-race", player.locale(), raceId)) - return@withContext true + return color + } + + suspend fun createImage(x1: Int, x2: Int, y1: Int, y2: Int): String = withContext(Dispatchers.IO) { + val image = BufferedImage(abs(x1 - x2) + 9, abs(y1 - y2) + 9, BufferedImage.TYPE_3BYTE_BGR) + + val sizeX = abs(x1 - x2) + 8 + val sizeY = abs(y1 - y2) + 8 + for (x in min(x1, x2) - 4..max(x1, x2) + 4) { + for (y in min(y1, y2) - 4..max(y1, y2) + 4) { + val relectiveX = sizeX - (x - (min(x1, x2) - 4)) + val relectiveY = sizeY - (y - (min(y1, y2) - 4)) + val hex = getBlockColor(x, y, Bukkit.getWorld("world")!!).replace("#", "") + val rgb = hex.toInt(16) + image.setRGB(relectiveX, relectiveY, rgb) + } } - if (!existStaff(raceId, player)) { - player.sendMessage(Lang.getComponent("only-race-creator-can-setting", player.locale())) - return@withContext true + + val baos = ByteArrayOutputStream() + val bos = BufferedOutputStream(baos) + withContext(Dispatchers.IO) { + ImageIO.write(image, "png", bos) + bos.flush() + bos.close() } - return@withContext false + return@withContext Base64.getEncoder().encodeToString(baos.toByteArray()) } fun judgeLap(goalDegree: Int, beforeDegree: Int?, currentDegree: Int?, threshold: Int): Int { @@ -102,6 +128,7 @@ object Utils { return -1 } } + 90 -> { if ((beforeDegree in goalDegree - threshold until goalDegree) && (currentDegree in goalDegree until goalDegree + threshold)) { return 1 @@ -110,6 +137,7 @@ object Utils { return -1 } } + 180 -> { if ((beforeDegree in goalDegree - threshold until goalDegree) && (currentDegree in goalDegree until goalDegree + threshold)) { return 1 @@ -118,6 +146,7 @@ object Utils { return -1 } } + 270 -> { if ((beforeDegree in goalDegree - threshold until goalDegree) && (currentDegree in goalDegree until goalDegree + threshold)) { return 1 @@ -163,4 +192,25 @@ object Utils { return UUID.fromString(this) } + fun UUID.toOfflinePlayer(): OfflinePlayer { + return Bukkit.getOfflinePlayer(this) + } + + fun passwordHash(string: String): String { + val sha3 = MessageDigest.getInstance("SHA3-256") + val sha3Result = sha3.digest(string.toByteArray()) + return String.format("%040x", BigInteger(1, sha3Result)) + } + + fun UUID.toLivingHorse(): Horse? { + return Bukkit.getEntity(this) as? Horse + } + + fun Component.toPlainText(): String { + return PlainTextComponentSerializer.plainText().serialize(this) + } + + val client = + OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS).readTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS).build() + } \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/utils/coroutines/MinecraftCoroutineUtil.kt b/src/main/kotlin/dev/nikomaru/raceassist/utils/coroutines/MinecraftCoroutineUtil.kt index 7c10919..1d00965 100644 --- a/src/main/kotlin/dev/nikomaru/raceassist/utils/coroutines/MinecraftCoroutineUtil.kt +++ b/src/main/kotlin/dev/nikomaru/raceassist/utils/coroutines/MinecraftCoroutineUtil.kt @@ -17,10 +17,11 @@ package dev.nikomaru.raceassist.utils.coroutines +import kotlinx.coroutines.Dispatchers import kotlin.coroutines.CoroutineContext -val async: CoroutineContext +val Dispatchers.async: CoroutineContext get() = DispatcherContainer.async -val minecraft: CoroutineContext +val Dispatchers.minecraft: CoroutineContext get() = DispatcherContainer.sync \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/web/WebCommand.kt b/src/main/kotlin/dev/nikomaru/raceassist/web/WebCommand.kt new file mode 100644 index 0000000..7927df0 --- /dev/null +++ b/src/main/kotlin/dev/nikomaru/raceassist/web/WebCommand.kt @@ -0,0 +1,82 @@ +/* + * Copyright © 2021-2022 Nikomaru + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package dev.nikomaru.raceassist.web + +import cloud.commandframework.annotations.* +import dev.nikomaru.raceassist.data.database.UserAuthData +import dev.nikomaru.raceassist.utils.Utils.passwordHash +import kotlinx.coroutines.Dispatchers +import org.apache.commons.lang.RandomStringUtils +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction + +@CommandMethod("ra|RaceAssist web") +@CommandPermission("raceassist.commands.web") +class WebCommand { + + @CommandMethod("register") + @CommandDescription("アカウントを登録します") + suspend fun register(sender: CommandSender) { + if (sender !is Player) return + val uuid = sender.uniqueId + + val exist = newSuspendedTransaction(Dispatchers.IO) { + UserAuthData.select(UserAuthData.uuid eq uuid.toString()).count() > 0 + } + if (exist) { + sender.sendMessage("すでに登録されています リセットする場合は /ra web resetを実行してください") + return + } + val password = RandomStringUtils.randomAlphanumeric(20) + val hashedPassword = passwordHash(password) + newSuspendedTransaction(Dispatchers.IO) { + UserAuthData.insert { + it[UserAuthData.uuid] = uuid.toString() + it[UserAuthData.hashedPassword] = hashedPassword + } + } + sender.sendRichMessage("パスワードは $password です クリックでコピー") + } + + @CommandMethod("reset") + @CommandDescription("アカウントをリセットします") + suspend fun reset(sender: CommandSender) { + if (sender !is Player) return + val uuid = sender.uniqueId + + val exist = newSuspendedTransaction(Dispatchers.IO) { + UserAuthData.select(UserAuthData.uuid eq uuid.toString()).count() > 0 + } + if (!exist) { + sender.sendRichMessage("登録されていません まずは /ra web registerを実行してください") + return + } + val password = RandomStringUtils.randomAlphanumeric(20) + val hashedPassword = passwordHash(password) + newSuspendedTransaction(Dispatchers.IO) { + UserAuthData.update({ UserAuthData.uuid eq uuid.toString() }) { + it[UserAuthData.hashedPassword] = hashedPassword + } + } + sender.sendRichMessage("パスワードは $password です クリックでコピー") + } + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/web/api/WebAPI.kt b/src/main/kotlin/dev/nikomaru/raceassist/web/api/WebAPI.kt new file mode 100644 index 0000000..1f3b439 --- /dev/null +++ b/src/main/kotlin/dev/nikomaru/raceassist/web/api/WebAPI.kt @@ -0,0 +1,248 @@ +/* + * Copyright © 2021-2022 Nikomaru + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package dev.nikomaru.raceassist.web.api + +import com.auth0.jwk.JwkProviderBuilder +import com.auth0.jwt.JWT +import com.auth0.jwt.algorithms.Algorithm +import dev.nikomaru.raceassist.RaceAssist +import dev.nikomaru.raceassist.bet.BetUtils +import dev.nikomaru.raceassist.data.database.UserAuthData +import dev.nikomaru.raceassist.data.files.* +import dev.nikomaru.raceassist.files.Config +import dev.nikomaru.raceassist.horse.data.HorseData +import dev.nikomaru.raceassist.horse.utlis.HorseUtils.getBirthDate +import dev.nikomaru.raceassist.horse.utlis.HorseUtils.getBreaderUniqueId +import dev.nikomaru.raceassist.horse.utlis.HorseUtils.getCalcJump +import dev.nikomaru.raceassist.horse.utlis.HorseUtils.getCalcMaxHealth +import dev.nikomaru.raceassist.horse.utlis.HorseUtils.getCalcSpeed +import dev.nikomaru.raceassist.horse.utlis.HorseUtils.getDeathDate +import dev.nikomaru.raceassist.horse.utlis.HorseUtils.getFatherUniqueId +import dev.nikomaru.raceassist.horse.utlis.HorseUtils.getHistories +import dev.nikomaru.raceassist.horse.utlis.HorseUtils.getMotherUniqueId +import dev.nikomaru.raceassist.utils.Utils.passwordHash +import dev.nikomaru.raceassist.utils.Utils.toLivingHorse +import dev.nikomaru.raceassist.utils.Utils.toOfflinePlayer +import dev.nikomaru.raceassist.utils.Utils.toPlainText +import dev.nikomaru.raceassist.utils.Utils.toUUID +import dev.nikomaru.raceassist.web.data.WebRaceJockeyData +import dev.nikomaru.raceassist.web.data.WebRaceJockeyDatas +import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.* +import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.auth.jwt.* +import io.ktor.server.engine.* +import io.ktor.server.http.content.* +import io.ktor.server.netty.* +import io.ktor.server.plugins.contentnegotiation.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import kotlinx.coroutines.Dispatchers +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import org.bukkit.Bukkit +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.select +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import java.security.KeyFactory +import java.security.KeyStore +import java.security.interfaces.RSAPrivateKey +import java.security.interfaces.RSAPublicKey +import java.security.spec.PKCS8EncodedKeySpec +import java.time.ZonedDateTime +import java.util.* +import java.util.concurrent.* + +object WebAPI { + + private val jwtConfig = Config.config.webAPI?.jwtConfig!! + val myRealm = jwtConfig.realm + val issuer = jwtConfig.issuer + val audience = jwtConfig.audience + val privateKey = jwtConfig.privateKey + val keyId = jwtConfig.keyId + + fun startServer() { + val keyStoreFile = RaceAssist.plugin.dataFolder.resolve("keystore.jks") + val keystore = KeyStore.getInstance(keyStoreFile, Config.config.webAPI!!.sslSetting.keyStorePassword.toCharArray()) + + val environment = applicationEngineEnvironment { + connector { + port = Config.config.webAPI!!.port + } + sslConnector(keyStore = keystore, + keyAlias = Config.config.webAPI!!.sslSetting.keyAlias, + keyStorePassword = { Config.config.webAPI!!.sslSetting.keyStorePassword.toCharArray() }, + privateKeyPassword = { Config.config.webAPI!!.sslSetting.privateKeyPassword.toCharArray() }) { + port = Config.config.webAPI!!.sslPort + keyStorePath = keyStoreFile + } + module(Application::module) + } + + embeddedServer(Netty, environment).start(wait = true) + } + +} + +private fun Application.module() { + install(ContentNegotiation) { + json() + } + val jwkProvider = JwkProviderBuilder(WebAPI.issuer).cached(10, 24, TimeUnit.HOURS).rateLimited(10, 1, TimeUnit.MINUTES).build() + install(Authentication) { + jwt("auth-jwt") { + realm = WebAPI.myRealm + verifier(jwkProvider, WebAPI.issuer) { + acceptLeeway(3) + } + validate { credential -> + if (credential.payload.getClaim("username").asString() == credential.payload.getClaim("uuid").asString().toUUID() + .toOfflinePlayer().name) { + JWTPrincipal(credential.payload) + } else { + null + } + } + challenge { _, _ -> + call.respond(HttpStatusCode.Unauthorized, "Token is not valid or has expired") + } + + } + + } + + routing { + route(("/api/v1")) { + get("/") { + call.respondText("Hello World!") + } + + } + route("/login") { + post { + val userData = call.receive() + val publicKey = jwkProvider.get(WebAPI.keyId).publicKey + val keySpecPKCS8 = PKCS8EncodedKeySpec(Base64.getDecoder().decode(WebAPI.privateKey)) + val privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpecPKCS8) + + val username = userData.username + val password = userData.password + + val offlinePlayer = Bukkit.getOfflinePlayer(username) + if (!offlinePlayer.hasPlayedBefore()) { + //401 + call.respond(HttpStatusCode.Unauthorized, "This player has never played before") + } + val uuid = offlinePlayer.uniqueId + + //パスワードの検証 + + val exist = newSuspendedTransaction(Dispatchers.IO) { + UserAuthData.select(UserAuthData.uuid eq uuid.toString()).count() > 0 + } + if (!exist) { + //401 + call.respond(HttpStatusCode.Unauthorized, "This player is not registered") + } + if (offlinePlayer.isBanned) { + //403 + call.respond(HttpStatusCode.Forbidden, "This player is banned") + } + val registeredPassword = newSuspendedTransaction { + val rr = UserAuthData.select { UserAuthData.uuid eq uuid.toString() }.first() + rr[UserAuthData.hashedPassword] + } + if (passwordHash(password) != registeredPassword) { + //401 + call.respond(HttpStatusCode.Unauthorized, "Password is incorrect") + } + + val token = JWT.create().withAudience(*(WebAPI.audience.toTypedArray())).withIssuer(WebAPI.issuer).withClaim("uuid", uuid.toString()) + .withClaim("username", username).withExpiresAt(Date(System.currentTimeMillis() + 30_000)) + .sign(Algorithm.RSA256(publicKey as RSAPublicKey, privateKey as RSAPrivateKey)) + call.respond(hashMapOf("token" to token)) + } + } + route("/horse") { + get("{uuid?}") { + val uuid = call.parameters["uuid"]?.toUUID() ?: return@get call.respondText("Missing id", status = HttpStatusCode.BadRequest) + val horse = uuid.toLivingHorse() ?: return@get call.respondText("Horse not found", status = HttpStatusCode.NotFound) + + val horseData = HorseData( + horse.uniqueId, + horse.getBreaderUniqueId(), + horse.ownerUniqueId!!, + horse.getMotherUniqueId(), + horse.getFatherUniqueId(), + horse.getHistories(), + horse.color.name, + horse.style.name, + horse.getCalcSpeed(), + horse.getCalcJump(), + horse.getCalcMaxHealth(), + horse.customName()?.toPlainText(), + horse.getBirthDate(), + ZonedDateTime.now(), + horse.getDeathDate(), + ) + + val horseDataJson = json.encodeToString(horseData) + call.respondText(horseDataJson, status = HttpStatusCode.OK, contentType = ContentType.Application.Json) + } + } + route("/jockeys") { + get("{raceId?}") { + val raceId = call.parameters["raceId"] ?: return@get call.respondText("Missing id", status = HttpStatusCode.BadRequest) + if (!RaceSettingData.existsRace(raceId)) { + call.respondText("Race not found", status = HttpStatusCode.NotFound) + } + val jockeys = RaceSettingData.getJockeys(raceId) + val jockeyDatas = arrayListOf() + + jockeys.forEach { + val webRaceJockeyData = + WebRaceJockeyData(it.uniqueId, RaceSettingData.getHorse(raceId)[it.uniqueId], BetUtils.getOdds(raceId, it)) + jockeyDatas.add(webRaceJockeyData) + } + + val datas = WebRaceJockeyDatas(raceId, BetSettingData.getBetUnit(raceId), jockeyDatas) + val datasJson = json.encodeToString(datas) + + call.respondText(datasJson, status = HttpStatusCode.OK, contentType = ContentType.Application.Json) + } + } + authenticate("auth-jwt") { + get("/hello") { + val principal = call.principal() + val uuid = principal!!.payload.getClaim("uuid").asString() + val expiresAt = principal.expiresAt?.time?.minus(System.currentTimeMillis()) + call.respondText("Hello, ${uuid.toUUID().toOfflinePlayer().name}! Token is expired at $expiresAt ms.") + } + } + static(".well-known") { + staticRootFolder = RaceAssist.plugin.dataFolder + file("jwks.json") + } + } +} + +@Serializable +data class UserData(val username: String, val password: String) \ No newline at end of file diff --git a/src/main/kotlin/dev/nikomaru/raceassist/web/data/WebDataFormats.kt b/src/main/kotlin/dev/nikomaru/raceassist/web/data/WebDataFormats.kt new file mode 100644 index 0000000..6df8cc1 --- /dev/null +++ b/src/main/kotlin/dev/nikomaru/raceassist/web/data/WebDataFormats.kt @@ -0,0 +1,39 @@ +/* + * Copyright © 2021-2022 Nikomaru + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package dev.nikomaru.raceassist.web.data + +import dev.nikomaru.raceassist.data.files.UUIDSerializer +import kotlinx.serialization.Serializable +import java.util.* + +@Serializable +data class WebBetDatas(val list: WebBetData, val betUnit: Int) + +@Serializable +data class WebBetData(val jockey: @Serializable(with = UUIDSerializer::class) UUID, var betPerUnit: Int) + +@Serializable +data class WebRaceJockeyDatas(val raceId: String, val betUnit: Int, val datas: ArrayList) + +@Serializable +data class WebRaceJockeyData(val jockey: @Serializable(with = UUIDSerializer::class) UUID, + val horse: @Serializable(with = UUIDSerializer::class) UUID?, + val odds: Double) + +@Serializable +data class History(val raceId: String, val rank: Int) \ No newline at end of file diff --git a/src/main/resources/MapColorDefault.properties b/src/main/resources/MapColorDefault.properties new file mode 100644 index 0000000..a51d5b9 --- /dev/null +++ b/src/main/resources/MapColorDefault.properties @@ -0,0 +1,936 @@ +# +#Sat Aug 20 15:50:09 JST 2022 +minecraft\:andesite_wall=#707070 +minecraft\:gray_bed=#4C4C4C +minecraft\:warped_slab=#3A8E8C +minecraft\:jungle_slab=#976D4D +minecraft\:crimson_stairs=#943F61 +minecraft\:purple_candle_cake=#000000 +minecraft\:fire=#000000 +minecraft\:lily_pad=#007C00 +minecraft\:deepslate_tiles=#646464 +minecraft\:deepslate_tile_stairs=#646464 +minecraft\:observer=#707070 +minecraft\:blackstone_stairs=#191919 +minecraft\:warped_fence=#3A8E8C +minecraft\:bubble_coral_fan=#7F3FB2 +minecraft\:polished_blackstone_brick_stairs=#191919 +minecraft\:pink_wool=#F27FA5 +minecraft\:light_gray_shulker_box=#999999 +minecraft\:mangrove_pressure_plate=#993333 +minecraft\:green_banner=#8F7748 +minecraft\:sculk=#191919 +minecraft\:orange_stained_glass_pane=#000000 +minecraft\:mangrove_fence=#993333 +minecraft\:kelp_plant=#000000 +minecraft\:granite_stairs=#976D4D +minecraft\:fire_coral_block=#993333 +minecraft\:potted_dead_bush=#000000 +minecraft\:blue_ice=#A0A0FF +minecraft\:light_blue_stained_glass=#6699D8 +minecraft\:dead_horn_coral_fan=#4C4C4C +minecraft\:yellow_candle_cake=#000000 +minecraft\:loom=#8F7748 +minecraft\:lime_shulker_box=#7FCC19 +minecraft\:stripped_crimson_stem=#943F61 +minecraft\:beacon=#5CDBD5 +minecraft\:dead_brain_coral=#4C4C4C +minecraft\:diorite_stairs=#FFFCF5 +minecraft\:yellow_candle=#E5E533 +minecraft\:stripped_mangrove_log=#993333 +minecraft\:green_carpet=#667F33 +minecraft\:zombie_head=#000000 +minecraft\:gray_banner=#8F7748 +minecraft\:stripped_acacia_log=#D87F33 +minecraft\:chiseled_quartz_block=#FFFCF5 +minecraft\:diorite_wall=#FFFCF5 +minecraft\:exposed_cut_copper_slab=#876B62 +minecraft\:lever=#000000 +minecraft\:mossy_stone_bricks=#707070 +minecraft\:tnt=#FF0000 +minecraft\:crimson_sign=#943F61 +minecraft\:black_carpet=#191919 +minecraft\:gray_terracotta=#392923 +minecraft\:white_carpet=#FFFFFF +minecraft\:end_stone_bricks=#F7E9A3 +minecraft\:purple_wall_banner=#000000 +minecraft\:sandstone_slab=#F7E9A3 +minecraft\:birch_trapdoor=#F7E9A3 +minecraft\:jungle_fence=#976D4D +minecraft\:smooth_stone_slab=#707070 +minecraft\:air=#000000 +minecraft\:end_stone_brick_slab=#F7E9A3 +minecraft\:stripped_birch_wood=#F7E9A3 +minecraft\:warped_nylium=#167E86 +minecraft\:void_air=#000000 +minecraft\:red_sandstone=#D87F33 +minecraft\:dark_oak_leaves=#007C00 +minecraft\:waxed_oxidized_cut_copper_stairs=#167E86 +minecraft\:deepslate_copper_ore=#646464 +minecraft\:bubble_coral=#7F3FB2 +minecraft\:potted_wither_rose=#000000 +minecraft\:wither_skeleton_skull=#000000 +minecraft\:light_weighted_pressure_plate=#FAEE4D +minecraft\:orange_glazed_terracotta=#D87F33 +minecraft\:mangrove_leaves=#007C00 +minecraft\:end_portal_frame=#667F33 +minecraft\:tuff=#392923 +minecraft\:purpur_stairs=#B24CD8 +minecraft\:magma_block=#700200 +minecraft\:cobblestone_slab=#707070 +minecraft\:creeper_wall_head=#000000 +minecraft\:red_nether_bricks=#700200 +minecraft\:waxed_oxidized_copper=#167E86 +minecraft\:terracotta=#D87F33 +minecraft\:dead_fire_coral_block=#4C4C4C +minecraft\:crimson_fungus=#700200 +minecraft\:repeating_command_block=#7F3FB2 +minecraft\:blue_wall_banner=#000000 +minecraft\:dirt=#976D4D +minecraft\:stripped_dark_oak_log=#664C33 +minecraft\:pink_concrete_powder=#F27FA5 +minecraft\:oak_planks=#8F7748 +minecraft\:cut_sandstone=#F7E9A3 +minecraft\:black_terracotta=#251610 +minecraft\:spruce_leaves=#007C00 +minecraft\:end_stone=#F7E9A3 +minecraft\:jungle_log=#976D4D +minecraft\:yellow_terracotta=#BA8524 +minecraft\:mud_bricks=#876B62 +minecraft\:mangrove_wall_sign=#000000 +minecraft\:red_sandstone_stairs=#D87F33 +minecraft\:yellow_bed=#E5E533 +minecraft\:pink_candle=#F27FA5 +minecraft\:dead_bubble_coral=#4C4C4C +minecraft\:redstone_torch=#000000 +minecraft\:carved_pumpkin=#D87F33 +minecraft\:oak_log=#8F7748 +minecraft\:mud=#575C5C +minecraft\:end_stone_brick_wall=#F7E9A3 +minecraft\:magenta_banner=#8F7748 +minecraft\:light=#000000 +minecraft\:soul_torch=#000000 +minecraft\:mossy_cobblestone=#707070 +minecraft\:calcite=#D1B1A1 +minecraft\:magenta_candle_cake=#000000 +minecraft\:orange_banner=#8F7748 +minecraft\:dragon_wall_head=#000000 +minecraft\:jungle_pressure_plate=#976D4D +minecraft\:barrier=#000000 +minecraft\:dark_oak_door=#664C33 +minecraft\:spruce_sapling=#007C00 +minecraft\:acacia_pressure_plate=#D87F33 +minecraft\:exposed_cut_copper=#876B62 +minecraft\:magenta_carpet=#B24CD8 +minecraft\:frogspawn=#4040FF +minecraft\:crimson_roots=#700200 +minecraft\:stripped_jungle_wood=#976D4D +minecraft\:jungle_button=#000000 +minecraft\:acacia_log=#D87F33 +minecraft\:ladder=#000000 +minecraft\:basalt=#191919 +minecraft\:slime_block=#7FB238 +minecraft\:lapis_block=#4A80FF +minecraft\:prismarine=#4C7F99 +minecraft\:iron_block=#A7A7A7 +minecraft\:reinforced_deepslate=#646464 +minecraft\:bell=#FAEE4D +minecraft\:candle_cake=#000000 +minecraft\:brick_slab=#993333 +minecraft\:gravel=#707070 +minecraft\:light_gray_stained_glass_pane=#000000 +minecraft\:light_gray_concrete_powder=#999999 +minecraft\:birch_wood=#F7E9A3 +minecraft\:dark_prismarine_stairs=#5CDBD5 +minecraft\:lime_banner=#8F7748 +minecraft\:orange_terracotta=#9F5224 +minecraft\:pink_stained_glass=#F27FA5 +minecraft\:glow_lichen=#7FA796 +minecraft\:black_concrete=#191919 +minecraft\:brown_candle=#664C33 +minecraft\:dark_oak_sapling=#007C00 +minecraft\:conduit=#5CDBD5 +minecraft\:piston_head=#000000 +minecraft\:black_wool=#191919 +minecraft\:brain_coral_fan=#F27FA5 +minecraft\:light_gray_carpet=#999999 +minecraft\:flowering_azalea_leaves=#007C00 +minecraft\:potatoes=#000000 +minecraft\:coarse_dirt=#976D4D +minecraft\:dark_oak_log=#664C33 +minecraft\:cyan_glazed_terracotta=#4C7F99 +minecraft\:brown_mushroom=#664C33 +minecraft\:stripped_warped_stem=#3A8E8C +minecraft\:waxed_oxidized_cut_copper_slab=#167E86 +minecraft\:potted_orange_tulip=#000000 +minecraft\:black_candle_cake=#000000 +minecraft\:crimson_trapdoor=#943F61 +minecraft\:light_blue_candle_cake=#000000 +minecraft\:dark_oak_fence_gate=#664C33 +minecraft\:dark_oak_stairs=#664C33 +minecraft\:lime_stained_glass_pane=#000000 +minecraft\:waxed_copper_block=#D87F33 +minecraft\:waxed_oxidized_cut_copper=#167E86 +minecraft\:dead_brain_coral_block=#4C4C4C +minecraft\:grass_block=#7FB238 +minecraft\:magenta_concrete_powder=#B24CD8 +minecraft\:green_terracotta=#4C522A +minecraft\:water=#2e4eaf +minecraft\:netherite_block=#191919 +minecraft\:smooth_red_sandstone_stairs=#D87F33 +minecraft\:polished_andesite=#707070 +minecraft\:oak_wall_sign=#000000 +minecraft\:acacia_leaves=#007C00 +minecraft\:acacia_door=#D87F33 +minecraft\:flower_pot=#000000 +minecraft\:quartz_slab=#FFFCF5 +minecraft\:lime_carpet=#7FCC19 +minecraft\:white_banner=#8F7748 +minecraft\:shroomlight=#993333 +minecraft\:smooth_quartz_stairs=#FFFCF5 +minecraft\:potted_dandelion=#000000 +minecraft\:cobweb=#C7C7C7 +minecraft\:weeping_vines=#700200 +minecraft\:stripped_spruce_log=#815631 +minecraft\:sandstone=#F7E9A3 +minecraft\:oak_door=#8F7748 +minecraft\:dead_horn_coral_wall_fan=#000000 +minecraft\:pointed_dripstone=#4C3223 +minecraft\:warped_hyphae=#562C3E +minecraft\:water_cauldron=#2e4eaf +minecraft\:scaffolding=#F7E9A3 +minecraft\:large_amethyst_bud=#7F3FB2 +minecraft\:cartography_table=#8F7748 +minecraft\:crimson_wall_sign=#000000 +minecraft\:white_candle_cake=#000000 +minecraft\:lime_concrete_powder=#7FCC19 +minecraft\:birch_pressure_plate=#F7E9A3 +minecraft\:spruce_sign=#815631 +minecraft\:crimson_fence_gate=#943F61 +minecraft\:yellow_wall_banner=#000000 +minecraft\:stone_brick_wall=#707070 +minecraft\:mangrove_wood=#993333 +minecraft\:stripped_oak_log=#8F7748 +minecraft\:red_shulker_box=#993333 +minecraft\:damaged_anvil=#A7A7A7 +minecraft\:lily_of_the_valley=#007C00 +minecraft\:end_stone_brick_stairs=#F7E9A3 +minecraft\:purple_glazed_terracotta=#7F3FB2 +minecraft\:orange_wall_banner=#000000 +minecraft\:bubble_coral_block=#7F3FB2 +minecraft\:green_candle_cake=#000000 +minecraft\:brown_terracotta=#4C3223 +minecraft\:kelp=#4040FF +minecraft\:light_gray_bed=#999999 +minecraft\:weathered_cut_copper_stairs=#3A8E8C +minecraft\:soul_fire=#000000 +minecraft\:sandstone_wall=#F7E9A3 +minecraft\:polished_blackstone_wall=#191919 +minecraft\:waxed_exposed_cut_copper_stairs=#876B62 +minecraft\:smooth_stone=#707070 +minecraft\:moving_piston=#000000 +minecraft\:quartz_bricks=#FFFCF5 +minecraft\:polished_diorite_stairs=#FFFCF5 +minecraft\:black_shulker_box=#191919 +minecraft\:comparator=#000000 +minecraft\:potted_flowering_azalea_bush=#000000 +minecraft\:chain_command_block=#667F33 +minecraft\:oak_stairs=#8F7748 +minecraft\:gray_candle=#4C4C4C +minecraft\:grass=#007C00 +minecraft\:white_stained_glass=#FFFFFF +minecraft\:warped_wart_block=#14B485 +minecraft\:small_dripleaf=#007C00 +minecraft\:moss_block=#667F33 +minecraft\:tube_coral=#334CB2 +minecraft\:polished_blackstone_bricks=#191919 +minecraft\:birch_fence=#F7E9A3 +minecraft\:light_gray_wool=#999999 +minecraft\:green_stained_glass=#667F33 +minecraft\:cave_vines=#000000 +minecraft\:sand=#F7E9A3 +minecraft\:cut_copper=#D87F33 +minecraft\:purpur_slab=#B24CD8 +minecraft\:light_blue_carpet=#6699D8 +minecraft\:orange_candle_cake=#000000 +minecraft\:warped_button=#000000 +minecraft\:mangrove_button=#000000 +minecraft\:warped_planks=#3A8E8C +minecraft\:cyan_wool=#4C7F99 +minecraft\:fletching_table=#8F7748 +minecraft\:light_blue_glazed_terracotta=#6699D8 +minecraft\:smooth_quartz_slab=#FFFCF5 +minecraft\:polished_granite_stairs=#976D4D +minecraft\:lime_stained_glass=#7FCC19 +minecraft\:composter=#8F7748 +minecraft\:cyan_terracotta=#575C5C +minecraft\:acacia_wall_sign=#000000 +minecraft\:waxed_weathered_copper=#3A8E8C +minecraft\:chorus_flower=#7F3FB2 +minecraft\:pink_terracotta=#A04D4E +minecraft\:birch_sign=#F7E9A3 +minecraft\:acacia_fence=#D87F33 +minecraft\:horn_coral_wall_fan=#000000 +minecraft\:end_portal=#000000 +minecraft\:magenta_concrete=#B24CD8 +minecraft\:lectern=#8F7748 +minecraft\:dark_prismarine_slab=#5CDBD5 +minecraft\:oxidized_cut_copper_slab=#167E86 +minecraft\:dark_oak_button=#000000 +minecraft\:campfire=#815631 +minecraft\:frosted_ice=#000000 +minecraft\:powder_snow=#FFFFFF +minecraft\:azalea=#007C00 +minecraft\:andesite_slab=#707070 +minecraft\:light_gray_stained_glass=#999999 +minecraft\:white_bed=#FFFFFF +minecraft\:red_concrete_powder=#993333 +minecraft\:warped_stem=#3A8E8C +minecraft\:green_concrete=#667F33 +minecraft\:red_tulip=#007C00 +minecraft\:jigsaw=#999999 +minecraft\:podzol=#815631 +minecraft\:white_concrete_powder=#FFFFFF +minecraft\:lightning_rod=#D87F33 +minecraft\:light_blue_bed=#6699D8 +minecraft\:orange_shulker_box=#D87F33 +minecraft\:stripped_crimson_hyphae=#5C191D +minecraft\:birch_log=#F7E9A3 +minecraft\:light_gray_wall_banner=#000000 +minecraft\:spruce_wall_sign=#000000 +minecraft\:cyan_candle=#4C7F99 +minecraft\:cyan_bed=#4C7F99 +minecraft\:brown_mushroom_block=#976D4D +minecraft\:azure_bluet=#007C00 +minecraft\:polished_deepslate_wall=#646464 +minecraft\:red_stained_glass=#993333 +minecraft\:purple_wool=#7F3FB2 +minecraft\:waxed_exposed_cut_copper_slab=#876B62 +minecraft\:red_nether_brick_stairs=#700200 +minecraft\:polished_andesite_slab=#707070 +minecraft\:brain_coral_wall_fan=#000000 +minecraft\:melon=#7FCC19 +minecraft\:mossy_stone_brick_stairs=#707070 +minecraft\:diamond_ore=#707070 +minecraft\:dragon_head=#000000 +minecraft\:diorite=#FFFCF5 +minecraft\:waxed_weathered_cut_copper_slab=#3A8E8C +minecraft\:light_gray_banner=#8F7748 +minecraft\:infested_chiseled_stone_bricks=#A4A8B8 +minecraft\:polished_andesite_stairs=#707070 +minecraft\:iron_door=#A7A7A7 +minecraft\:lime_bed=#7FCC19 +minecraft\:soul_wall_torch=#000000 +minecraft\:polished_basalt=#191919 +minecraft\:mangrove_sign=#993333 +minecraft\:black_glazed_terracotta=#191919 +minecraft\:ender_chest=#707070 +minecraft\:dark_oak_trapdoor=#664C33 +minecraft\:beetroots=#000000 +minecraft\:black_candle=#191919 +minecraft\:infested_deepslate=#646464 +minecraft\:mud_brick_slab=#664C33 +minecraft\:brick_wall=#993333 +minecraft\:black_stained_glass_pane=#000000 +minecraft\:orange_bed=#D87F33 +minecraft\:lilac=#007C00 +minecraft\:potted_cornflower=#000000 +minecraft\:respawn_anchor=#191919 +minecraft\:dead_brain_coral_wall_fan=#000000 +minecraft\:nether_brick_wall=#700200 +minecraft\:acacia_slab=#D87F33 +minecraft\:polished_diorite_slab=#FFFCF5 +minecraft\:jack_o_lantern=#D87F33 +minecraft\:mangrove_roots=#815631 +minecraft\:cactus=#007C00 +minecraft\:purple_stained_glass_pane=#000000 +minecraft\:pink_shulker_box=#F27FA5 +minecraft\:crimson_door=#943F61 +minecraft\:twisting_vines=#4C7F99 +minecraft\:weathered_cut_copper_slab=#3A8E8C +minecraft\:blue_glazed_terracotta=#334CB2 +minecraft\:brain_coral_block=#F27FA5 +minecraft\:white_glazed_terracotta=#FFFFFF +minecraft\:sandstone_stairs=#F7E9A3 +minecraft\:player_head=#000000 +minecraft\:smoker=#707070 +minecraft\:large_fern=#007C00 +minecraft\:magenta_stained_glass=#B24CD8 +minecraft\:purple_terracotta=#7A4958 +minecraft\:nether_brick_fence=#700200 +minecraft\:note_block=#8F7748 +minecraft\:light_blue_concrete=#6699D8 +minecraft\:dead_bubble_coral_fan=#4C4C4C +minecraft\:petrified_oak_slab=#8F7748 +minecraft\:birch_wall_sign=#000000 +minecraft\:brown_carpet=#664C33 +minecraft\:soul_soil=#664C33 +minecraft\:stone_stairs=#707070 +minecraft\:player_wall_head=#000000 +minecraft\:sticky_piston=#707070 +minecraft\:granite_wall=#976D4D +minecraft\:snow=#FFFFFF +minecraft\:red_bed=#993333 +minecraft\:cyan_stained_glass=#4C7F99 +minecraft\:glass_pane=#000000 +minecraft\:sculk_sensor=#4C7F99 +minecraft\:wall_torch=#000000 +minecraft\:oak_trapdoor=#8F7748 +minecraft\:crimson_nylium=#BD3031 +minecraft\:diamond_block=#5CDBD5 +minecraft\:birch_sapling=#007C00 +minecraft\:acacia_button=#000000 +minecraft\:dead_bush=#8F7748 +minecraft\:farmland=#976D4D +minecraft\:stone_slab=#707070 +minecraft\:bedrock=#707070 +minecraft\:infested_mossy_stone_bricks=#A4A8B8 +minecraft\:potted_cactus=#000000 +minecraft\:potted_oxeye_daisy=#000000 +minecraft\:purple_candle=#7F3FB2 +minecraft\:activator_rail=#000000 +minecraft\:oak_pressure_plate=#8F7748 +minecraft\:smooth_quartz=#FFFCF5 +minecraft\:glowstone=#F7E9A3 +minecraft\:spawner=#707070 +minecraft\:light_gray_glazed_terracotta=#999999 +minecraft\:white_tulip=#007C00 +minecraft\:tube_coral_fan=#334CB2 +minecraft\:purple_concrete=#7F3FB2 +minecraft\:light_blue_stained_glass_pane=#000000 +minecraft\:oak_fence_gate=#8F7748 +minecraft\:tripwire_hook=#000000 +minecraft\:black_concrete_powder=#191919 +minecraft\:spruce_button=#000000 +minecraft\:cracked_deepslate_tiles=#646464 +minecraft\:prismarine_wall=#4C7F99 +minecraft\:chipped_anvil=#A7A7A7 +minecraft\:redstone_wall_torch=#000000 +minecraft\:stone_brick_stairs=#707070 +minecraft\:potted_crimson_roots=#000000 +minecraft\:lava=#ea5c0f +minecraft\:gold_ore=#707070 +minecraft\:light_blue_wool=#6699D8 +minecraft\:blue_shulker_box=#334CB2 +minecraft\:birch_stairs=#F7E9A3 +minecraft\:tinted_glass=#4C4C4C +minecraft\:polished_blackstone_brick_slab=#191919 +minecraft\:daylight_detector=#8F7748 +minecraft\:pink_concrete=#F27FA5 +minecraft\:dead_fire_coral_wall_fan=#000000 +minecraft\:weeping_vines_plant=#000000 +minecraft\:quartz_block=#FFFCF5 +minecraft\:fire_coral=#993333 +minecraft\:polished_deepslate=#646464 +minecraft\:coal_ore=#707070 +minecraft\:jungle_door=#976D4D +minecraft\:deepslate_iron_ore=#646464 +minecraft\:blue_carpet=#334CB2 +minecraft\:polished_deepslate_stairs=#646464 +minecraft\:spruce_door=#815631 +minecraft\:black_wall_banner=#000000 +minecraft\:jungle_leaves=#007C00 +minecraft\:cobblestone=#707070 +minecraft\:powered_rail=#000000 +minecraft\:stripped_dark_oak_wood=#664C33 +minecraft\:exposed_cut_copper_stairs=#876B62 +minecraft\:waxed_cut_copper_stairs=#D87F33 +minecraft\:brown_bed=#664C33 +minecraft\:command_block=#664C33 +minecraft\:honeycomb_block=#D87F33 +minecraft\:gray_concrete=#4C4C4C +minecraft\:light_blue_terracotta=#706C8A +minecraft\:smooth_sandstone_slab=#F7E9A3 +minecraft\:blast_furnace=#707070 +minecraft\:horn_coral=#E5E533 +minecraft\:beehive=#8F7748 +minecraft\:pink_carpet=#F27FA5 +minecraft\:attached_melon_stem=#000000 +minecraft\:moss_carpet=#667F33 +minecraft\:lapis_ore=#707070 +minecraft\:cut_red_sandstone=#D87F33 +minecraft\:anvil=#A7A7A7 +minecraft\:potted_brown_mushroom=#000000 +minecraft\:brown_stained_glass_pane=#000000 +minecraft\:obsidian=#191919 +minecraft\:red_sandstone_wall=#D87F33 +minecraft\:purple_concrete_powder=#7F3FB2 +minecraft\:cobbled_deepslate_stairs=#646464 +minecraft\:chiseled_stone_bricks=#707070 +minecraft\:blue_candle_cake=#000000 +minecraft\:chiseled_deepslate=#646464 +minecraft\:clay=#A4A8B8 +minecraft\:soul_campfire=#815631 +minecraft\:gray_concrete_powder=#4C4C4C +minecraft\:warped_roots=#4C7F99 +minecraft\:yellow_glazed_terracotta=#E5E533 +minecraft\:yellow_wool=#E5E533 +minecraft\:hanging_roots=#976D4D +minecraft\:polished_deepslate_slab=#646464 +minecraft\:dead_tube_coral=#4C4C4C +minecraft\:brown_concrete_powder=#664C33 +minecraft\:acacia_fence_gate=#D87F33 +minecraft\:pink_wall_banner=#000000 +minecraft\:red_sand=#D87F33 +minecraft\:pearlescent_froglight=#F27FA5 +minecraft\:oak_button=#000000 +minecraft\:ochre_froglight=#F7E9A3 +minecraft\:sunflower=#007C00 +minecraft\:cake=#000000 +minecraft\:stone_bricks=#707070 +minecraft\:crimson_pressure_plate=#943F61 +minecraft\:enchanting_table=#993333 +minecraft\:gray_wall_banner=#000000 +minecraft\:cut_sandstone_slab=#F7E9A3 +minecraft\:lantern=#A7A7A7 +minecraft\:cobblestone_stairs=#707070 +minecraft\:poppy=#007C00 +minecraft\:stone=#707070 +minecraft\:potted_crimson_fungus=#000000 +minecraft\:white_shulker_box=#FFFFFF +minecraft\:potted_azure_bluet=#000000 +minecraft\:potted_spruce_sapling=#000000 +minecraft\:seagrass=#4040FF +minecraft\:redstone_ore=#707070 +minecraft\:potted_dark_oak_sapling=#000000 +minecraft\:jungle_sapling=#007C00 +minecraft\:turtle_egg=#F7E9A3 +minecraft\:amethyst_block=#7F3FB2 +minecraft\:big_dripleaf=#007C00 +minecraft\:waxed_weathered_cut_copper=#3A8E8C +minecraft\:cut_red_sandstone_slab=#D87F33 +minecraft\:birch_door=#F7E9A3 +minecraft\:jungle_fence_gate=#976D4D +minecraft\:oak_slab=#8F7748 +minecraft\:oxidized_cut_copper_stairs=#167E86 +minecraft\:dark_prismarine=#5CDBD5 +minecraft\:ancient_debris=#191919 +minecraft\:spruce_slab=#815631 +minecraft\:white_terracotta=#D1B1A1 +minecraft\:peony=#007C00 +minecraft\:acacia_wood=#4C4C4C +minecraft\:sea_lantern=#FFFCF5 +minecraft\:big_dripleaf_stem=#000000 +minecraft\:skeleton_skull=#000000 +minecraft\:dirt_path=#976D4D +minecraft\:mossy_cobblestone_wall=#707070 +minecraft\:white_wool=#FFFFFF +minecraft\:purple_stained_glass=#7F3FB2 +minecraft\:blue_stained_glass_pane=#000000 +minecraft\:deepslate_emerald_ore=#646464 +minecraft\:small_amethyst_bud=#7F3FB2 +minecraft\:polished_blackstone_slab=#191919 +minecraft\:light_blue_candle=#6699D8 +minecraft\:deepslate_brick_slab=#646464 +minecraft\:end_rod=#000000 +minecraft\:smooth_basalt=#191919 +minecraft\:cave_vines_plant=#000000 +minecraft\:allium=#007C00 +minecraft\:magenta_wool=#B24CD8 +minecraft\:red_wall_banner=#000000 +minecraft\:prismarine_bricks=#5CDBD5 +minecraft\:brown_candle_cake=#000000 +minecraft\:mud_brick_stairs=#876B62 +minecraft\:gray_candle_cake=#000000 +minecraft\:crafting_table=#8F7748 +minecraft\:warped_pressure_plate=#3A8E8C +minecraft\:green_shulker_box=#667F33 +minecraft\:sculk_catalyst=#191919 +minecraft\:potted_mangrove_propagule=#000000 +minecraft\:lava_cauldron=#000000 +minecraft\:cut_copper_stairs=#D87F33 +minecraft\:dried_kelp_block=#667F33 +minecraft\:creeper_head=#000000 +minecraft\:red_carpet=#993333 +minecraft\:dark_oak_slab=#664C33 +minecraft\:bubble_coral_wall_fan=#000000 +minecraft\:prismarine_brick_slab=#5CDBD5 +minecraft\:warped_fungus=#4C7F99 +minecraft\:red_candle_cake=#000000 +minecraft\:pumpkin=#D87F33 +minecraft\:oak_sapling=#007C00 +minecraft\:barrel=#8F7748 +minecraft\:jungle_trapdoor=#976D4D +minecraft\:bookshelf=#8F7748 +minecraft\:blue_stained_glass=#334CB2 +minecraft\:light_gray_candle=#999999 +minecraft\:polished_blackstone_button=#000000 +minecraft\:dead_fire_coral_fan=#4C4C4C +minecraft\:potted_red_tulip=#000000 +minecraft\:potted_warped_roots=#000000 +minecraft\:chiseled_red_sandstone=#D87F33 +minecraft\:sea_pickle=#667F33 +minecraft\:orange_candle=#D87F33 +minecraft\:green_concrete_powder=#667F33 +minecraft\:copper_block=#D87F33 +minecraft\:weathered_copper=#3A8E8C +minecraft\:brown_stained_glass=#664C33 +minecraft\:attached_pumpkin_stem=#000000 +minecraft\:mangrove_door=#993333 +minecraft\:bamboo_sapling=#000000 +minecraft\:glass=#000000 +minecraft\:cyan_carpet=#4C7F99 +minecraft\:potted_red_mushroom=#000000 +minecraft\:blackstone_wall=#191919 +minecraft\:orange_wool=#D87F33 +minecraft\:polished_blackstone_pressure_plate=#191919 +minecraft\:green_stained_glass_pane=#000000 +minecraft\:polished_blackstone_brick_wall=#191919 +minecraft\:potted_allium=#000000 +minecraft\:piston=#707070 +minecraft\:purple_bed=#7F3FB2 +minecraft\:tube_coral_block=#334CB2 +minecraft\:spruce_trapdoor=#815631 +minecraft\:magenta_shulker_box=#B24CD8 +minecraft\:iron_ore=#707070 +minecraft\:smooth_sandstone_stairs=#F7E9A3 +minecraft\:exposed_copper=#876B62 +minecraft\:gilded_blackstone=#191919 +minecraft\:netherrack=#700200 +minecraft\:nether_brick_slab=#700200 +minecraft\:yellow_shulker_box=#E5E533 +minecraft\:lime_wall_banner=#000000 +minecraft\:gray_stained_glass_pane=#000000 +minecraft\:crimson_slab=#943F61 +minecraft\:purple_carpet=#7F3FB2 +minecraft\:blue_concrete_powder=#334CB2 +minecraft\:potted_fern=#000000 +minecraft\:furnace=#707070 +minecraft\:bee_nest=#E5E533 +minecraft\:smithing_table=#8F7748 +minecraft\:iron_trapdoor=#A7A7A7 +minecraft\:grindstone=#A7A7A7 +minecraft\:mangrove_fence_gate=#993333 +minecraft\:white_stained_glass_pane=#000000 +minecraft\:lime_wool=#7FCC19 +minecraft\:candle=#F7E9A3 +minecraft\:bricks=#993333 +minecraft\:brewing_stand=#A7A7A7 +minecraft\:stripped_spruce_wood=#815631 +minecraft\:spruce_fence=#815631 +minecraft\:red_sandstone_slab=#D87F33 +minecraft\:waxed_exposed_cut_copper=#876B62 +minecraft\:smooth_red_sandstone=#D87F33 +minecraft\:pink_stained_glass_pane=#000000 +minecraft\:cracked_polished_blackstone_bricks=#191919 +minecraft\:jungle_planks=#976D4D +minecraft\:chain=#000000 +minecraft\:dead_tube_coral_wall_fan=#000000 +minecraft\:dead_tube_coral_fan=#4C4C4C +minecraft\:rail=#000000 +minecraft\:jungle_stairs=#976D4D +minecraft\:cobbled_deepslate_wall=#646464 +minecraft\:purple_shulker_box=#7A4958 +minecraft\:end_gateway=#000000 +minecraft\:prismarine_stairs=#4C7F99 +minecraft\:zombie_wall_head=#000000 +minecraft\:sculk_vein=#191919 +minecraft\:vine=#007C00 +minecraft\:red_mushroom_block=#993333 +minecraft\:pink_tulip=#007C00 +minecraft\:flowering_azalea=#007C00 +minecraft\:sweet_berry_bush=#000000 +minecraft\:light_blue_shulker_box=#6699D8 +minecraft\:powder_snow_cauldron=#000000 +minecraft\:light_gray_candle_cake=#000000 +minecraft\:potted_birch_sapling=#000000 +minecraft\:weathered_cut_copper=#3A8E8C +minecraft\:trapped_chest=#8F7748 +minecraft\:dropper=#707070 +minecraft\:pink_bed=#F27FA5 +minecraft\:chest=#8F7748 +minecraft\:spruce_log=#815631 +minecraft\:red_nether_brick_slab=#700200 +minecraft\:mud_brick_wall=#876B62 +minecraft\:cauldron=#707070 +minecraft\:infested_stone_bricks=#A4A8B8 +minecraft\:chiseled_sandstone=#F7E9A3 +minecraft\:polished_granite=#976D4D +minecraft\:dispenser=#707070 +minecraft\:potted_blue_orchid=#000000 +minecraft\:pink_candle_cake=#000000 +minecraft\:polished_blackstone=#191919 +minecraft\:jungle_sign=#976D4D +minecraft\:light_gray_concrete=#999999 +minecraft\:infested_cobblestone=#A4A8B8 +minecraft\:acacia_sapling=#007C00 +minecraft\:red_banner=#8F7748 +minecraft\:tube_coral_wall_fan=#000000 +minecraft\:warped_fence_gate=#3A8E8C +minecraft\:deepslate_redstone_ore=#646464 +minecraft\:warped_sign=#3A8E8C +minecraft\:brick_stairs=#993333 +minecraft\:granite_slab=#976D4D +minecraft\:wet_sponge=#E5E533 +minecraft\:cracked_stone_bricks=#707070 +minecraft\:warped_stairs=#3A8E8C +minecraft\:yellow_carpet=#E5E533 +minecraft\:azalea_leaves=#007C00 +minecraft\:warped_trapdoor=#3A8E8C +minecraft\:heavy_weighted_pressure_plate=#A7A7A7 +minecraft\:redstone_block=#FF0000 +minecraft\:dead_horn_coral_block=#4C4C4C +minecraft\:yellow_stained_glass=#E5E533 +minecraft\:polished_granite_slab=#976D4D +minecraft\:lime_candle_cake=#000000 +minecraft\:cracked_deepslate_bricks=#646464 +minecraft\:deepslate_lapis_ore=#646464 +minecraft\:red_stained_glass_pane=#000000 +minecraft\:rose_bush=#007C00 +minecraft\:warped_wall_sign=#000000 +minecraft\:sponge=#E5E533 +minecraft\:crimson_button=#000000 +minecraft\:prismarine_slab=#4C7F99 +minecraft\:blue_candle=#334CB2 +minecraft\:cyan_stained_glass_pane=#000000 +minecraft\:red_mushroom=#993333 +minecraft\:gray_stained_glass=#4C4C4C +minecraft\:spruce_planks=#815631 +minecraft\:repeater=#000000 +minecraft\:andesite=#707070 +minecraft\:magenta_terracotta=#95576C +minecraft\:birch_planks=#F7E9A3 +minecraft\:crimson_hyphae=#5C191D +minecraft\:potted_lily_of_the_valley=#000000 +minecraft\:gray_glazed_terracotta=#4C4C4C +minecraft\:horn_coral_block=#E5E533 +minecraft\:spruce_pressure_plate=#815631 +minecraft\:raw_iron_block=#D8AF93 +minecraft\:potted_white_tulip=#000000 +minecraft\:mossy_cobblestone_slab=#707070 +minecraft\:dandelion=#007C00 +minecraft\:crying_obsidian=#191919 +minecraft\:oxeye_daisy=#007C00 +minecraft\:shulker_box=#7F3FB2 +minecraft\:deepslate_brick_wall=#646464 +minecraft\:brown_concrete=#664C33 +minecraft\:light_blue_banner=#8F7748 +minecraft\:cyan_concrete=#4C7F99 +minecraft\:stone_pressure_plate=#707070 +minecraft\:wither_rose=#007C00 +minecraft\:dripstone_block=#4C3223 +minecraft\:dark_oak_wood=#664C33 +minecraft\:prismarine_brick_stairs=#5CDBD5 +minecraft\:raw_gold_block=#FAEE4D +minecraft\:lime_concrete=#7FCC19 +minecraft\:cyan_concrete_powder=#4C7F99 +minecraft\:orange_stained_glass=#D87F33 +minecraft\:structure_void=#000000 +minecraft\:stonecutter=#707070 +minecraft\:cyan_candle_cake=#000000 +minecraft\:green_candle=#667F33 +minecraft\:light_gray_terracotta=#876B62 +minecraft\:smooth_red_sandstone_slab=#D87F33 +minecraft\:dark_oak_wall_sign=#000000 +minecraft\:mangrove_planks=#993333 +minecraft\:granite=#976D4D +minecraft\:tripwire=#000000 +minecraft\:potted_poppy=#000000 +minecraft\:waxed_cut_copper=#D87F33 +minecraft\:cobblestone_wall=#707070 +minecraft\:white_candle=#C7C7C7 +minecraft\:carrots=#000000 +minecraft\:polished_diorite=#FFFCF5 +minecraft\:orange_concrete_powder=#D87F33 +minecraft\:white_concrete=#FFFFFF +minecraft\:green_wall_banner=#000000 +minecraft\:chiseled_polished_blackstone=#191919 +minecraft\:mushroom_stem=#C7C7C7 +minecraft\:light_blue_wall_banner=#000000 +minecraft\:nether_brick_stairs=#700200 +minecraft\:hay_block=#E5E533 +minecraft\:purpur_pillar=#B24CD8 +minecraft\:wheat=#000000 +minecraft\:dark_oak_fence=#664C33 +minecraft\:verdant_froglight=#7FA796 +minecraft\:mossy_cobblestone_stairs=#707070 +minecraft\:dead_brain_coral_fan=#4C4C4C +minecraft\:jukebox=#976D4D +minecraft\:stripped_jungle_log=#976D4D +minecraft\:potted_bamboo=#000000 +minecraft\:orange_tulip=#007C00 +minecraft\:ice=#A0A0FF +minecraft\:snow_block=#FFFFFF +minecraft\:magenta_wall_banner=#000000 +minecraft\:crimson_fence=#943F61 +minecraft\:muddy_mangrove_roots=#815631 +minecraft\:infested_cracked_stone_bricks=#A4A8B8 +minecraft\:blue_bed=#334CB2 +minecraft\:red_wool=#993333 +minecraft\:birch_fence_gate=#F7E9A3 +minecraft\:crimson_planks=#943F61 +minecraft\:pumpkin_stem=#000000 +minecraft\:fire_coral_wall_fan=#000000 +minecraft\:blue_concrete=#334CB2 +minecraft\:structure_block=#999999 +minecraft\:horn_coral_fan=#E5E533 +minecraft\:spore_blossom=#007C00 +minecraft\:budding_amethyst=#7F3FB2 +minecraft\:oxidized_copper=#167E86 +minecraft\:nether_quartz_ore=#700200 +minecraft\:tall_seagrass=#000000 +minecraft\:lime_terracotta=#677535 +minecraft\:acacia_trapdoor=#D87F33 +minecraft\:magenta_stained_glass_pane=#000000 +minecraft\:brown_glazed_terracotta=#664C33 +minecraft\:nether_gold_ore=#700200 +minecraft\:mossy_stone_brick_wall=#707070 +minecraft\:lodestone=#A7A7A7 +minecraft\:brown_wool=#664C33 +minecraft\:yellow_concrete_powder=#E5E533 +minecraft\:quartz_pillar=#FFFCF5 +minecraft\:potted_warped_fungus=#000000 +minecraft\:acacia_stairs=#D87F33 +minecraft\:target=#FFFCF5 +minecraft\:jungle_wood=#976D4D +minecraft\:spruce_wood=#815631 +minecraft\:cyan_wall_banner=#000000 +minecraft\:deepslate_diamond_ore=#646464 +minecraft\:magenta_bed=#B24CD8 +minecraft\:soul_lantern=#A7A7A7 +minecraft\:nether_portal=#000000 +minecraft\:honey_block=#D87F33 +minecraft\:bubble_column=#000000 +minecraft\:cracked_nether_bricks=#700200 +minecraft\:gray_wool=#4C4C4C +minecraft\:dead_tube_coral_block=#4C4C4C +minecraft\:deepslate_brick_stairs=#646464 +minecraft\:nether_bricks=#700200 +minecraft\:oak_wood=#8F7748 +minecraft\:polished_blackstone_stairs=#191919 +minecraft\:blue_terracotta=#4C3E5C +minecraft\:deepslate_coal_ore=#646464 +minecraft\:green_bed=#667F33 +minecraft\:magenta_glazed_terracotta=#B24CD8 +minecraft\:redstone_wire=#000000 +minecraft\:diorite_slab=#FFFCF5 +minecraft\:stone_button=#000000 +minecraft\:brown_banner=#8F7748 +minecraft\:red_candle=#993333 +minecraft\:nether_sprouts=#4C7F99 +minecraft\:mangrove_log=#993333 +minecraft\:gold_block=#FAEE4D +minecraft\:yellow_banner=#8F7748 +minecraft\:amethyst_cluster=#7F3FB2 +minecraft\:waxed_weathered_cut_copper_stairs=#3A8E8C +minecraft\:detector_rail=#000000 +minecraft\:packed_mud=#976D4D +minecraft\:deepslate_tile_slab=#646464 +minecraft\:birch_leaves=#007C00 +minecraft\:purple_banner=#8F7748 +minecraft\:cocoa=#000000 +minecraft\:cave_air=#000000 +minecraft\:bone_block=#F7E9A3 +minecraft\:acacia_planks=#D87F33 +minecraft\:black_stained_glass=#191919 +minecraft\:pink_glazed_terracotta=#F27FA5 +minecraft\:cobbled_deepslate=#646464 +minecraft\:red_glazed_terracotta=#993333 +minecraft\:yellow_stained_glass_pane=#000000 +minecraft\:blackstone=#191919 +minecraft\:mangrove_propagule=#007C00 +minecraft\:hopper=#707070 +minecraft\:waxed_cut_copper_slab=#D87F33 +minecraft\:mossy_stone_brick_slab=#707070 +minecraft\:green_glazed_terracotta=#667F33 +minecraft\:orange_concrete=#D87F33 +minecraft\:orange_carpet=#D87F33 +minecraft\:fern=#007C00 +minecraft\:cut_copper_slab=#D87F33 +minecraft\:chiseled_nether_bricks=#700200 +minecraft\:brain_coral=#F27FA5 +minecraft\:black_bed=#191919 +minecraft\:redstone_lamp=#000000 +minecraft\:dark_oak_sign=#664C33 +minecraft\:quartz_stairs=#FFFCF5 +minecraft\:chorus_plant=#7F3FB2 +minecraft\:blue_orchid=#007C00 +minecraft\:oak_leaves=#007C00 +minecraft\:stone_brick_slab=#707070 +minecraft\:dragon_egg=#191919 +minecraft\:red_concrete=#993333 +minecraft\:brown_wall_banner=#000000 +minecraft\:potted_azalea_bush=#000000 +minecraft\:skeleton_wall_skull=#000000 +minecraft\:dark_oak_pressure_plate=#664C33 +minecraft\:pink_banner=#8F7748 +minecraft\:cyan_shulker_box=#4C7F99 +minecraft\:stripped_acacia_wood=#D87F33 +minecraft\:spruce_fence_gate=#815631 +minecraft\:potted_jungle_sapling=#000000 +minecraft\:blackstone_slab=#191919 +minecraft\:sculk_shrieker=#191919 +minecraft\:spruce_stairs=#815631 +minecraft\:magenta_candle=#B24CD8 +minecraft\:mangrove_slab=#993333 +minecraft\:nether_wart=#993333 +minecraft\:soul_sand=#664C33 +minecraft\:light_blue_concrete_powder=#6699D8 +minecraft\:dead_fire_coral=#4C4C4C +minecraft\:cornflower=#007C00 +minecraft\:deepslate_gold_ore=#646464 +minecraft\:nether_wart_block=#993333 +minecraft\:green_wool=#667F33 +minecraft\:lime_glazed_terracotta=#7FCC19 +minecraft\:deepslate_tile_wall=#646464 +minecraft\:white_wall_banner=#000000 +minecraft\:black_banner=#8F7748 +minecraft\:birch_button=#000000 +minecraft\:cyan_banner=#8F7748 +minecraft\:jungle_wall_sign=#000000 +minecraft\:waxed_exposed_copper=#876B62 +minecraft\:acacia_sign=#D87F33 +minecraft\:warped_door=#3A8E8C +minecraft\:deepslate_bricks=#646464 +minecraft\:purpur_block=#B24CD8 +minecraft\:oxidized_cut_copper=#167E86 +minecraft\:blue_banner=#8F7748 +minecraft\:infested_stone=#A4A8B8 +minecraft\:stripped_birch_log=#F7E9A3 +minecraft\:torch=#000000 +minecraft\:crimson_stem=#943F61 +minecraft\:blue_wool=#334CB2 +minecraft\:andesite_stairs=#707070 +minecraft\:melon_stem=#000000 +minecraft\:fire_coral_fan=#993333 +minecraft\:dead_horn_coral=#4C4C4C +minecraft\:wither_skeleton_wall_skull=#000000 +minecraft\:coal_block=#191919 +minecraft\:yellow_concrete=#E5E533 +minecraft\:mangrove_stairs=#993333 +minecraft\:packed_ice=#A0A0FF +minecraft\:potted_oak_sapling=#000000 +minecraft\:red_terracotta=#8E3C2E +minecraft\:stripped_warped_hyphae=#562C3E +minecraft\:stripped_oak_wood=#8F7748 +minecraft\:emerald_block=#00D93A +minecraft\:birch_slab=#F7E9A3 +minecraft\:smooth_sandstone=#F7E9A3 +minecraft\:iron_bars=#000000 +minecraft\:raw_copper_block=#D87F33 +minecraft\:mycelium=#7F3FB2 +minecraft\:bamboo=#007C00 +minecraft\:oak_sign=#8F7748 +minecraft\:gray_shulker_box=#4C4C4C +minecraft\:potted_pink_tulip=#000000 +minecraft\:gray_carpet=#4C4C4C +minecraft\:dead_bubble_coral_wall_fan=#000000 +minecraft\:medium_amethyst_bud=#7F3FB2 +minecraft\:sugar_cane=#007C00 +minecraft\:twisting_vines_plant=#000000 +minecraft\:lime_candle=#7FCC19 +minecraft\:brown_shulker_box=#664C33 +minecraft\:red_nether_brick_wall=#700200 +minecraft\:oak_fence=#8F7748 +minecraft\:deepslate=#646464 +minecraft\:dark_oak_planks=#664C33 +minecraft\:potted_acacia_sapling=#000000 +minecraft\:rooted_dirt=#976D4D +minecraft\:tall_grass=#007C00 +minecraft\:copper_ore=#707070 +minecraft\:mangrove_trapdoor=#993333 +minecraft\:stripped_mangrove_wood=#993333 +minecraft\:emerald_ore=#707070 +minecraft\:dead_bubble_coral_block=#4C4C4C +minecraft\:cobbled_deepslate_slab=#646464 + diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml deleted file mode 100644 index 9255bce..0000000 --- a/src/main/resources/config.yml +++ /dev/null @@ -1,5 +0,0 @@ -RaceSetting: - threshold: 40 - bet: 1000 -NetworkSettings: - discord: diff --git a/src/main/resources/lang/ja_JP.properties b/src/main/resources/lang/ja_JP.properties index 22e9a99..74f7db7 100644 --- a/src/main/resources/lang/ja_JP.properties +++ b/src/main/resources/lang/ja_JP.properties @@ -36,10 +36,12 @@ last-lap=最終ラップです max-player-is-eight=プレイヤーは最大で8人しか設定することはできません no-exist-central-point=中心点が存在しません no-exist-race=レースが存在しません -no-exist-this-raceid-race={0}のレースは存在しません +no-exist-this-raceid-race={0}のraceIdは存在しません +no-exist-this-placeid-race={0}のplaceIdは存在しません no-have-money=あなたにはお金がありません no-inside-course-set=内側のコースが設定されていません not-exsist-staff-this-player={0} は存在しません +not-exists-place not-find-staff=スタッフが見つかりませんでした not-found-this-race=そのレースは見つかりません now-betting-price={0}円単位 : {1}円かけています @@ -49,7 +51,8 @@ now-not-belong=参加していません now-you-not-setting-mode=あなたは設定モードに現在なっていません one-step-backwards-lap=ラップが一つ戻りました one-step-forward-lap=ラップが一つ進みました -only-race-creator-can-setting=他人のレースは設定できません +only-race-creator-can-setting=他人のraceIdは設定できません +only-place-creator-can-setting=他人のplaceIdは設定できません outside-the-racetrack=レース場外に出てる可能性があります over-two-users-need=開催には2人以上のユーザーが必要です paid-bet-creator={0}に{1}円支払いました