diff --git a/src/main/kotlin/dev/solsynth/snConnect/SolarNetworkConnect.kt b/src/main/kotlin/dev/solsynth/snConnect/SolarNetworkConnect.kt index 36c47d0..8987d22 100644 --- a/src/main/kotlin/dev/solsynth/snConnect/SolarNetworkConnect.kt +++ b/src/main/kotlin/dev/solsynth/snConnect/SolarNetworkConnect.kt @@ -12,7 +12,13 @@ import dev.solsynth.snConnect.models.SnChatMessage import dev.solsynth.snConnect.models.WebSocketPacket import dev.solsynth.snConnect.services.SnMessageService import dev.solsynth.snConnect.services.SnService -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch import net.md_5.bungee.api.ChatColor import net.md_5.bungee.api.chat.ClickEvent import net.md_5.bungee.api.chat.ComponentBuilder @@ -24,9 +30,12 @@ import org.bukkit.Bukkit.getOnlinePlayers import org.bukkit.configuration.file.YamlConfiguration import org.bukkit.plugin.java.JavaPlugin import java.io.InputStreamReader +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap class SolarNetworkConnect : JavaPlugin() { + private val pluginScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) private var economy: Economy? = null private var sn: SnService? = null private var authService: AuthService? = null @@ -34,9 +43,8 @@ class SolarNetworkConnect : JavaPlugin() { private var messageService: SnMessageService? = null private var syncChatRooms: List = emptyList() private var destinationChatId: String? = null - private var webSocketJob: Job? = null private var messages: Map = mapOf() - private var playerSolarpassMap: MutableMap = mutableMapOf() + private var playerSolarpassMap: ConcurrentHashMap = ConcurrentHashMap() private fun handleWebSocketPacket(packet: WebSocketPacket) { if (config.getBoolean("debug")) @@ -74,9 +82,11 @@ class SolarNetworkConnect : JavaPlugin() { } } val component = componentBuilder.create() - for (player in getOnlinePlayers()) { - player.spigot().sendMessage(*component) - } + Bukkit.getScheduler().runTask(this, Runnable { + for (player in getOnlinePlayers()) { + player.spigot().sendMessage(*component) + } + }) } } } @@ -109,17 +119,17 @@ class SolarNetworkConnect : JavaPlugin() { } if (messageService != null && destinationChatId != null) { - server.pluginManager.registerEvents(SnChatListener(messageService!!, destinationChatId!!, messages), this) + server.pluginManager.registerEvents(SnChatListener(pluginScope, messageService!!, destinationChatId!!, messages), this) } if (authUserService != null) { server.pluginManager.registerEvents( - SolarpassCheckListener(authUserService!!, messages, playerSolarpassMap), + SolarpassCheckListener(pluginScope, authUserService!!, messages, playerSolarpassMap), this ) } - Bukkit.getPluginCommand("solar")!!.setExecutor(SnCommand(this.sn!!, this.economy, messages, playerSolarpassMap)) + Bukkit.getPluginCommand("solar")!!.setExecutor(SnCommand(this, pluginScope, this.sn!!, this.economy, messages, playerSolarpassMap)) Bukkit.getPluginCommand("solar")!!.tabCompleter = SnCommandCompleter() Bukkit.getPluginCommand("eatrock")!!.setExecutor(BedrockRemoverCommand(this.economy)) @@ -135,7 +145,9 @@ class SolarNetworkConnect : JavaPlugin() { // Send server start message try { destinationChatId?.let { chatId -> - messageService?.sendMessage(chatId, messages["server_start"] ?: "🚀 Server started successfully") + pluginScope.launch { + messageService?.sendMessage(chatId, messages["server_start"] ?: "🚀 Server started successfully") + } } } catch (e: Exception) { logger.warning("Failed to send server start message: ${e.message}") @@ -148,14 +160,16 @@ class SolarNetworkConnect : JavaPlugin() { // Send server stop message try { destinationChatId?.let { chatId -> - messageService?.sendMessage(chatId, messages["server_stop"] ?: "âšī¸ Server stopped") + pluginScope.launch { + messageService?.sendMessage(chatId, messages["server_stop"] ?: "âšī¸ Server stopped") + } } } catch (e: Exception) { logger.warning("Failed to send server stop message: ${e.message}") } sn?.disconnect() - webSocketJob?.cancel() + pluginScope.cancel() } private fun setupNetwork(): Boolean { @@ -175,11 +189,19 @@ class SolarNetworkConnect : JavaPlugin() { authService = AuthService(authUrl, siteSecret); authUserService = AuthUserService(authService!!) - webSocketJob = GlobalScope.launch { + pluginScope.launch { sn!!.connectWebSocketAsFlow().collect { packet -> handleWebSocketPacket(packet) } } + + pluginScope.launch { + while(isActive) { + delay(60000) + sn?.sendPing() + } + } + return true; } diff --git a/src/main/kotlin/dev/solsynth/snConnect/commands/BedrockRemoverCommand.kt b/src/main/kotlin/dev/solsynth/snConnect/commands/BedrockRemoverCommand.kt index 490dac2..db5fd3e 100644 --- a/src/main/kotlin/dev/solsynth/snConnect/commands/BedrockRemoverCommand.kt +++ b/src/main/kotlin/dev/solsynth/snConnect/commands/BedrockRemoverCommand.kt @@ -14,11 +14,11 @@ import org.bukkit.inventory.ItemStack class BedrockRemoverCommand(private val economy: Economy?) : CommandExecutor { private val paymentMethods = listOf("money", "materials") - private val requiredMaterials = listOf( - Material.PISTON, - Material.TNT, - Material.LEVER, - Material.OAK_TRAPDOOR // Representative for trapdoor group + private val requiredMaterials = mapOf( + Material.PISTON to 1, + Material.TNT to 1, + Material.LEVER to 1, + Material.OAK_TRAPDOOR to 1 ) override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { @@ -27,41 +27,30 @@ class BedrockRemoverCommand(private val economy: Economy?) : CommandExecutor { return true } - if (args.isEmpty() || args.size > 1) { - sender.sendMessage(ChatColor.RED.toString() + "Usage: /bedrockremove ") + if (args.size != 1 || args[0].lowercase() !in paymentMethods) { + sender.sendMessage(ChatColor.RED.toString() + "Usage: /bedrockremove ") return true } - val paymentMethod = args[0].lowercase() - if (paymentMethod !in paymentMethods) { - sender.sendMessage(ChatColor.RED.toString() + "Invalid payment method. Use: money or materials.") - return true - } - - // Get the block the player is looking at val targetBlock = sender.getTargetBlock(null, 10) - if (targetBlock == null || targetBlock.type != Material.BEDROCK) { + if (targetBlock?.type != Material.BEDROCK) { sender.sendMessage(ChatColor.RED.toString() + "You must be looking at a bedrock block within 10 blocks.") return true } - // Attempt payment + val paymentMethod = args[0].lowercase() val success = when (paymentMethod) { "money" -> payWithMoney(sender, 100.0) - "materials" -> payWithAllMaterials(sender) - else -> return false + "materials" -> payWithMaterials(sender) + else -> false } - if (!success) { - // Error messages are handled in the pay methods - return true + if (success) { + targetBlock.type = Material.AIR + sender.sendMessage(ChatColor.GREEN.toString() + "Bedrock removed successfully!") + sender.playSound(sender.location, Sound.BLOCK_STONE_BREAK, 1.0f, 1.0f) } - // Remove the bedrock - targetBlock.type = Material.AIR - sender.sendMessage(ChatColor.GREEN.toString() + "Bedrock removed successfully!") - sender.playSound(sender.location, Sound.BLOCK_STONE_BREAK, 1.0f, 1.0f) - return true } @@ -70,136 +59,72 @@ class BedrockRemoverCommand(private val economy: Economy?) : CommandExecutor { player.sendMessage(ChatColor.RED.toString() + "Economy not available.") return false } - val response = economy.withdrawPlayer(player, "Bedrock removal", amount) + val response = economy.withdrawPlayer(player, amount) if (response.type != EconomyResponse.ResponseType.SUCCESS) { - player.sendMessage(ChatColor.RED.toString() + "Insufficient funds. You need ${amount}.") + player.sendMessage(ChatColor.RED.toString() + "Insufficient funds. You need ${economy.format(amount)}.") return false } - player.sendMessage(ChatColor.GREEN.toString() + "Withdrew $amount from your account.") + player.sendMessage(ChatColor.GREEN.toString() + "Withdrew ${economy.format(amount)} from your account.") return true } - private fun payWithMaterial(player: Player, material: Material): Boolean { + private fun payWithMaterials(player: Player): Boolean { val inventory = player.inventory + val itemsToRemove = mutableListOf() - // Special handling for materials with multiple variants - val hasItem = when (material) { - Material.OAK_TRAPDOOR -> { - // Check for any trapdoor type - inventory.contents.any { item -> item != null && item.type.name.endsWith("_TRAPDOOR") } - } - Material.PISTON -> { - // Check for any piston type - inventory.contents.any { item -> item != null && (item.type == Material.PISTON || item.type == Material.STICKY_PISTON) } - } - else -> inventory.contains(material, 1) - } - - if (!hasItem) { - player.sendMessage(ChatColor.RED.toString() + "You don't have a ${material.name.lowercase()} in your inventory.") - return false - } - - // Remove the item - when (material) { - Material.OAK_TRAPDOOR -> { - // Find and remove any trapdoor - for (item in inventory.contents) { - if (item != null && item.type.name.endsWith("_TRAPDOOR")) { - val amount = item.amount - if (amount > 1) { - item.amount = amount - 1 - } else { - inventory.remove(item) - } - break - } - } - } - Material.PISTON -> { - // Find and remove any piston - for (item in inventory.contents) { - if (item != null && (item.type == Material.PISTON || item.type == Material.STICKY_PISTON)) { - val amount = item.amount - if (amount > 1) { - item.amount = amount - 1 - } else { - inventory.remove(item) - } - break - } - } - } - else -> { - inventory.removeItem(ItemStack(material, 1)) - } - } - - player.sendMessage(ChatColor.GREEN.toString() + "Consumed 1 ${material.name.lowercase()}.") - return true - } - - private fun payWithAllMaterials(player: Player): Boolean { - val inventory = player.inventory - - // Check if player has all required materials - for (material in requiredMaterials) { - val hasItem = when (material) { - Material.OAK_TRAPDOOR -> { - // Check for any trapdoor type - inventory.contents.any { item -> item != null && item.type.name.endsWith("_TRAPDOOR") } - } - Material.PISTON -> { - // Check for any piston type - inventory.contents.any { item -> item != null && (item.type == Material.PISTON || item.type == Material.STICKY_PISTON) } - } - else -> inventory.contains(material, 1) - } - if (!hasItem) { - player.sendMessage(ChatColor.RED.toString() + "You don't have all required materials: piston, tnt, lever, and trapdoor.") + for ((material, amount) in requiredMaterials) { + val foundItems = findRequiredItems(inventory, material, amount) + if (foundItems == null) { + player.sendMessage(ChatColor.RED.toString() + "You don't have all required materials: piston, TNT, lever, and a trapdoor.") return false } + itemsToRemove.addAll(foundItems) } - // Remove one of each material - for (material in requiredMaterials) { - when (material) { - Material.OAK_TRAPDOOR -> { - // Find and remove any trapdoor - for (item in inventory.contents) { - if (item != null && item.type.name.endsWith("_TRAPDOOR")) { - val amount = item.amount - if (amount > 1) { - item.amount = amount - 1 - } else { - inventory.remove(item) - } - break - } - } - } - Material.PISTON -> { - // Find and remove any piston - for (item in inventory.contents) { - if (item != null && (item.type == Material.PISTON || item.type == Material.STICKY_PISTON)) { - val amount = item.amount - if (amount > 1) { - item.amount = amount - 1 - } else { - inventory.remove(item) - } - break - } - } - } - else -> { - inventory.removeItem(ItemStack(material, 1)) - } + // This check-then-act approach has a potential race condition if the player + // modifies their inventory between the check and the removal. For this plugin, + // the risk is low, but in a more critical system, a more robust transactional + // approach would be needed. + inventory.removeItem(*itemsToRemove.toTypedArray()) + + player.sendMessage(ChatColor.GREEN.toString() + "Consumed materials: piston, TNT, lever, trapdoor.") + return true + } + + private fun findRequiredItems(inventory: org.bukkit.inventory.Inventory, material: Material, requiredAmount: Int): List? { + val foundItems = mutableListOf() + var amountFound = 0 + + for (item in inventory.contents) { + if (item != null && isMaterialMatch(item.type, material)) { + amountFound += item.amount + foundItems.add(item) } } - player.sendMessage(ChatColor.GREEN.toString() + "Consumed materials: piston, tnt, lever, trapdoor.") - return true + if (amountFound >= requiredAmount) { + // We have enough, now determine the exact stacks to remove + val toRemove = mutableListOf() + var amountToRemove = requiredAmount + for (item in foundItems) { + val itemClone = item.clone() + if (amountToRemove <= 0) break + val amountFromThisStack = itemClone.amount.coerceAtMost(amountToRemove) + itemClone.amount = amountFromThisStack + toRemove.add(itemClone) + amountToRemove -= amountFromThisStack + } + return toRemove + } + return null + } + + private fun isMaterialMatch(itemType: Material, requiredMaterial: Material): Boolean { + return when (requiredMaterial) { + Material.OAK_TRAPDOOR -> itemType.name.endsWith("_TRAPDOOR") + Material.PISTON -> itemType == Material.PISTON || itemType == Material.STICKY_PISTON + else -> itemType == requiredMaterial + } } } @@ -212,9 +137,10 @@ class BedrockRemoverCompleter : TabCompleter { label: String, args: Array ): MutableList { - return when (args.size) { - 1 -> paymentMethods.filter { it.startsWith(args[0], ignoreCase = true) }.toMutableList() - else -> mutableListOf() + return if (args.size == 1) { + paymentMethods.filter { it.startsWith(args[0], ignoreCase = true) }.toMutableList() + } else { + mutableListOf() } } } diff --git a/src/main/kotlin/dev/solsynth/snConnect/commands/SnCommand.kt b/src/main/kotlin/dev/solsynth/snConnect/commands/SnCommand.kt index f3d06f9..7390dd3 100644 --- a/src/main/kotlin/dev/solsynth/snConnect/commands/SnCommand.kt +++ b/src/main/kotlin/dev/solsynth/snConnect/commands/SnCommand.kt @@ -1,11 +1,12 @@ package dev.solsynth.snConnect.commands import dev.solsynth.snConnect.services.* +import kotlinx.coroutines.CoroutineScope import net.md_5.bungee.api.chat.ClickEvent import net.md_5.bungee.api.chat.TextComponent import net.milkbowl.vault.economy.Economy import net.milkbowl.vault.economy.EconomyResponse -import org.bukkit.Bukkit.getLogger +import org.bukkit.Bukkit import org.bukkit.ChatColor import org.bukkit.Sound import org.bukkit.command.Command @@ -13,12 +14,18 @@ import org.bukkit.command.CommandExecutor import org.bukkit.command.CommandSender import org.bukkit.command.TabCompleter import org.bukkit.entity.Player +import org.bukkit.plugin.java.JavaPlugin +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap +import kotlinx.coroutines.launch class SnCommand( + private val plugin: JavaPlugin, + private val pluginScope: CoroutineScope, private val sn: SnService, private val eco: Economy?, private val messages: Map, - private val playerSolarpassMap: MutableMap + private val playerSolarpassMap: ConcurrentHashMap ) : CommandExecutor { override fun onCommand(p0: CommandSender, p1: Command, p2: String, p3: Array): Boolean { @@ -48,42 +55,45 @@ class SnCommand( ?: "Confirming payment, please stand by...") ); - val order: SnOrder - try { - order = orderSrv.getOrder(orderNumber); - // The update validated the order is created by us - orderSrv.updateOrderStatus(orderNumber, false); - } catch (_: Exception) { - p0.sendMessage( - ChatColor.RED.toString() + (messages["command_deposit_pull_error"] - ?: "An error occurred while pulling transaction. Make sure the order exists then try again later.") - ) - return true; + pluginScope.launch { + try { + val order = orderSrv.getOrder(orderNumber); + // The update validated the order is created by us + orderSrv.updateOrderStatus(orderNumber, false); + + Bukkit.getScheduler().runTask(plugin, Runnable { + if (order.status != 1L) { + p0.sendMessage( + ChatColor.RED.toString() + (messages["command_deposit_order_not_paid"] + ?: "Order was not paid yet.") + ) + return@Runnable + } + + val bal = order.amount; + eco?.depositPlayer(p0, bal) + + val doneComponent = + TextComponent(ChatColor.GREEN.toString() + (messages["command_deposit_done"] ?: "Done!")) + val moneyComponent = TextComponent(ChatColor.GOLD.toString() + ChatColor.BOLD + " $bal$") + val suffixComponent = + TextComponent( + ChatColor.GREEN.toString() + (messages["command_deposit_added_balance"] + ?: " has been added to your balance.") + ) + + p0.playSound(p0.location, Sound.BLOCK_ANVIL_PLACE, 1.0F, 1.0F) + p0.spigot().sendMessage(doneComponent, moneyComponent, suffixComponent) + }) + } catch (_: Exception) { + Bukkit.getScheduler().runTask(plugin, Runnable { + p0.sendMessage( + ChatColor.RED.toString() + (messages["command_deposit_pull_error"] + ?: "An error occurred while pulling transaction. Make sure the order exists then try again later.") + ) + }) + } } - - if (order.status != 1L) { - p0.sendMessage( - ChatColor.RED.toString() + (messages["command_deposit_order_not_paid"] - ?: "Order was not paid yet.") - ) - return true; - } - - val bal = order.amount; - eco?.depositPlayer(p0.player, bal) - - val doneComponent = - TextComponent(ChatColor.GREEN.toString() + (messages["command_deposit_done"] ?: "Done!")) - val moneyComponent = TextComponent(ChatColor.GOLD.toString() + ChatColor.BOLD + " $bal$") - val suffixComponent = - TextComponent( - ChatColor.GREEN.toString() + (messages["command_deposit_added_balance"] - ?: " has been added to your balance.") - ) - - p0.playSound(p0.player!!, Sound.BLOCK_ANVIL_PLACE, 1.0F, 1.0F) - p0.spigot().sendMessage(doneComponent, moneyComponent, suffixComponent) - return true; } @@ -102,44 +112,47 @@ class SnCommand( ?: "Creating order, please stand by...") ); - val order: SnOrder - try { - order = orderSrv.createOrder("Deposit to GoatCraft", amount); - } catch (e: Exception) { - p0.sendMessage( - ChatColor.RED.toString() + (messages["command_deposit_create_error"] - ?: "An error occurred while creating order. Try again later.") - ) - e.printStackTrace(); - return true; + pluginScope.launch { + try { + val order = orderSrv.createOrder("Deposit to GoatCraft", amount); + Bukkit.getScheduler().runTask(plugin, Runnable { + val linkComponent = + TextComponent( + ChatColor.GOLD.toString() + ChatColor.UNDERLINE.toString() + ChatColor.BOLD.toString() + (messages["command_deposit_click_here"] + ?: "click here") + ) + linkComponent.clickEvent = + ClickEvent(ClickEvent.Action.OPEN_URL, "https://solian.app/orders/${order.id}"); + val orderHintComponent = + TextComponent( + ChatColor.GRAY.toString() + (messages["command_deposit_order_created"] + ?: "Order created, number ") + ChatColor.WHITE + ChatColor.BOLD + "#${order.id.take(6)}" + " " + ) + val followingComponent = TextComponent( + ChatColor.GRAY.toString() + (messages["command_deposit_to_payment_page"] ?: " to the payment page.") + ) + p0.spigot() + .sendMessage(orderHintComponent, linkComponent, followingComponent); + + val afterPaidComponent = + TextComponent( + ChatColor.UNDERLINE.toString() + ChatColor.YELLOW + (messages["command_deposit_after_paid"] + ?: "After you paid, click here to confirm payment.") + ) + afterPaidComponent.clickEvent = + ClickEvent(ClickEvent.Action.RUN_COMMAND, "/sn deposit confirm ${order.id}") + p0.spigot().sendMessage(afterPaidComponent); + }) + } catch (e: Exception) { + Bukkit.getScheduler().runTask(plugin, Runnable { + p0.sendMessage( + ChatColor.RED.toString() + (messages["command_deposit_create_error"] + ?: "An error occurred while creating order. Try again later.") + ) + e.printStackTrace(); + }) + } } - - val linkComponent = - TextComponent( - ChatColor.GOLD.toString() + ChatColor.UNDERLINE.toString() + ChatColor.BOLD.toString() + (messages["command_deposit_click_here"] - ?: "click here") - ) - linkComponent.clickEvent = - ClickEvent(ClickEvent.Action.OPEN_URL, "https://solian.app/orders/${order.id}"); - val orderHintComponent = - TextComponent( - ChatColor.GRAY.toString() + (messages["command_deposit_order_created"] - ?: "Order created, number ") + ChatColor.WHITE + ChatColor.BOLD + "#${order.id.take(6)}" + " " - ) - val followingComponent = TextComponent( - ChatColor.GRAY.toString() + (messages["command_deposit_to_payment_page"] ?: " to the payment page.") - ) - p0.spigot() - .sendMessage(orderHintComponent, linkComponent, followingComponent); - - val afterPaidComponent = - TextComponent( - ChatColor.UNDERLINE.toString() + ChatColor.YELLOW + (messages["command_deposit_after_paid"] - ?: "After you paid, click here to confirm payment.") - ) - afterPaidComponent.clickEvent = - ClickEvent(ClickEvent.Action.RUN_COMMAND, "/sn deposit confirm ${order.id}") - p0.spigot().sendMessage(afterPaidComponent); } "withdraw" -> { @@ -160,7 +173,7 @@ class SnCommand( return true; } - val playerUuid = p0.uniqueId.toString() + val playerUuid = p0.uniqueId val accountId = playerSolarpassMap[playerUuid] if (accountId == null) { p0.sendMessage( @@ -176,7 +189,7 @@ class SnCommand( // Takes extra 20% as fee val fee = amount * 0.2; - val resp = eco?.withdrawPlayer(p0.player, amount + fee); + val resp = eco?.withdrawPlayer(p0, amount + fee); if (resp?.type != EconomyResponse.ResponseType.SUCCESS) { p0.sendMessage( ChatColor.RED.toString() + (messages["command_withdraw_no_money"] @@ -185,25 +198,31 @@ class SnCommand( return true; } - try { - val transactionSrv = SnBalanceService(sn); - transactionSrv.addBalance(amount, "Withdraw from GoatCraft", accountId); - val transactionHintComponent = - TextComponent( - ChatColor.GREEN.toString() + (messages["command_withdraw_done"] - ?: "Done! ") - ) + pluginScope.launch { + try { + val transactionSrv = SnBalanceService(sn); + transactionSrv.addBalance(amount, "Withdraw from GoatCraft", accountId); - p0.playSound(p0.player!!, Sound.BLOCK_ANVIL_PLACE, 1.0F, 1.0F) - p0.spigot().sendMessage(transactionHintComponent) - } catch (e: Exception) { - eco.depositPlayer(p0.player, amount + fee) - p0.sendMessage( - ChatColor.RED.toString() + (messages["command_withdraw_error"] - ?: "An error occurred while making transaction. Make sure your wallet exists then try again later.") - ) - e.printStackTrace() - return true + Bukkit.getScheduler().runTask(plugin, Runnable { + val transactionHintComponent = + TextComponent( + ChatColor.GREEN.toString() + (messages["command_withdraw_done"] + ?: "Done! ") + ) + + p0.playSound(p0.location, Sound.BLOCK_ANVIL_PLACE, 1.0F, 1.0F) + p0.spigot().sendMessage(transactionHintComponent) + }) + } catch (e: Exception) { + Bukkit.getScheduler().runTask(plugin, Runnable { + eco.depositPlayer(p0, amount + fee) + p0.sendMessage( + ChatColor.RED.toString() + (messages["command_withdraw_error"] + ?: "An error occurred while making transaction. Make sure your wallet exists then try again later.") + ) + e.printStackTrace() + }) + } } } diff --git a/src/main/kotlin/dev/solsynth/snConnect/listeners/SnChatListener.kt b/src/main/kotlin/dev/solsynth/snConnect/listeners/SnChatListener.kt index 84841cd..a80aaad 100644 --- a/src/main/kotlin/dev/solsynth/snConnect/listeners/SnChatListener.kt +++ b/src/main/kotlin/dev/solsynth/snConnect/listeners/SnChatListener.kt @@ -1,19 +1,18 @@ package dev.solsynth.snConnect.listeners -import com.Zrips.CMI.CMI import dev.solsynth.snConnect.services.SnMessageService -import org.bukkit.Bukkit -import org.bukkit.entity.Player +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority import org.bukkit.event.Listener import org.bukkit.event.entity.PlayerDeathEvent import org.bukkit.event.player.AsyncPlayerChatEvent -import org.bukkit.event.player.PlayerAdvancementDoneEvent import org.bukkit.event.player.PlayerJoinEvent import org.bukkit.event.player.PlayerQuitEvent class SnChatListener( + private val pluginScope: CoroutineScope, private val messageService: SnMessageService, private val destinationChatId: String, private val messages: Map @@ -21,31 +20,38 @@ class SnChatListener( @EventHandler(priority = EventPriority.MONITOR) fun onPlayerChat(event: AsyncPlayerChatEvent) { val message = "${event.player.name}: ${event.message}" - messageService.sendMessage(destinationChatId, message) + pluginScope.launch { + messageService.sendMessage(destinationChatId, message) + } } - @Suppress("SENSELESS_COMPARISON") @EventHandler() fun onPlayerJoin(event: PlayerJoinEvent) { - val firstTime = Bukkit.getOfflinePlayer(event.player.uniqueId) == null; - val templateKey = if (!firstTime) "join" else "join_first"; + val firstTime = !event.player.hasPlayedBefore(); + val templateKey = if (firstTime) "join_first" else "join"; val template = messages[templateKey] - ?: if (!firstTime) "âžĄī¸ {player} joined the game." else "âžĄī¸ {player} first time joined the game." + ?: if (firstTime) "âžĄī¸ {player} first time joined the game." else "âžĄī¸ {player} joined the game." val message = template.replace("{player}", event.player.name) - messageService.sendMessage(destinationChatId, message) + pluginScope.launch { + messageService.sendMessage(destinationChatId, message) + } } @EventHandler() fun onPlayerQuit(event: PlayerQuitEvent) { val template = messages["quit"] ?: "âŦ…ī¸ {player} left the game." val message = template.replace("{player}", event.player.name) - messageService.sendMessage(destinationChatId, message) + pluginScope.launch { + messageService.sendMessage(destinationChatId, message) + } } @EventHandler() fun onPlayerDeath(event: PlayerDeathEvent) { - val template = messages["death"] ?: "💀 {player} {message}" + val template = messages["death"] ?: "💀 {message}" val message = template.replace("{player}", event.entity.name).replace("{message}", event.deathMessage ?: "") - messageService.sendMessage(destinationChatId, message) + pluginScope.launch { + messageService.sendMessage(destinationChatId, message) + } } } diff --git a/src/main/kotlin/dev/solsynth/snConnect/listeners/SolarpassCheckListener.kt b/src/main/kotlin/dev/solsynth/snConnect/listeners/SolarpassCheckListener.kt index b152c97..6e4582d 100644 --- a/src/main/kotlin/dev/solsynth/snConnect/listeners/SolarpassCheckListener.kt +++ b/src/main/kotlin/dev/solsynth/snConnect/listeners/SolarpassCheckListener.kt @@ -1,33 +1,41 @@ package dev.solsynth.snConnect.listeners import dev.solsynth.snConnect.services.AuthUserService -import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.bukkit.Bukkit import org.bukkit.ChatColor import org.bukkit.event.EventHandler import org.bukkit.event.Listener import org.bukkit.event.player.PlayerJoinEvent +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap class SolarpassCheckListener( + private val pluginScope: CoroutineScope, private val authUserService: AuthUserService, private val messages: Map, - private val playerSolarpassMap: MutableMap + private val playerSolarpassMap: ConcurrentHashMap ) : Listener { @EventHandler() fun onPlayerJoin(event: PlayerJoinEvent) { - GlobalScope.launch { + pluginScope.launch { try { - val playerUuid = event.player.uniqueId.toString() - var solarpassId = playerSolarpassMap[playerUuid] - if (solarpassId == null) { - solarpassId = authUserService.getSnByPlayer(playerUuid) - playerSolarpassMap[playerUuid] = solarpassId + val playerUuid = event.player.uniqueId + if (playerSolarpassMap.containsKey(playerUuid)) { + return@launch } + + val solarpassId = authUserService.getSnByPlayer(playerUuid.toString()) + playerSolarpassMap[playerUuid] = solarpassId + if (solarpassId == null) { // Send suggestion message val message = messages["solarpass_bind_suggestion"] ?: "${ChatColor.YELLOW}To get more features, please bind your Solarpass account!" - event.player.sendMessage(message) + Bukkit.getScheduler().runTask(Bukkit.getPluginManager().getPlugin("SolarNetworkConnect")!!, Runnable { + event.player.sendMessage(message) + }) } } catch (e: Exception) { // Optionally log the error or handle it diff --git a/src/main/kotlin/dev/solsynth/snConnect/services/SnService.kt b/src/main/kotlin/dev/solsynth/snConnect/services/SnService.kt index b75801f..14faf52 100644 --- a/src/main/kotlin/dev/solsynth/snConnect/services/SnService.kt +++ b/src/main/kotlin/dev/solsynth/snConnect/services/SnService.kt @@ -95,4 +95,8 @@ class SnService(private val baseUrl: String, val clientId: String, val clientSec websocket?.close(1000, "Disconnecting") websocket = null } + + fun sendPing() { + websocket?.send("{\"type\":\"ping\"}") + } }