diff --git a/src/main/kotlin/dev/solsynth/snConnect/SolarNetworkConnect.kt b/src/main/kotlin/dev/solsynth/snConnect/SolarNetworkConnect.kt index 90a975a..835fd93 100644 --- a/src/main/kotlin/dev/solsynth/snConnect/SolarNetworkConnect.kt +++ b/src/main/kotlin/dev/solsynth/snConnect/SolarNetworkConnect.kt @@ -7,8 +7,7 @@ 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.GlobalScope -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import net.md_5.bungee.api.ChatColor import net.md_5.bungee.api.chat.ClickEvent import net.md_5.bungee.api.chat.ComponentBuilder @@ -26,6 +25,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 fun handleWebSocketPacket(packet: WebSocketPacket) { // logger.info("Received WebSocket packet: type=${packet.type}") @@ -79,6 +80,13 @@ class SolarNetworkConnect : JavaPlugin() { this.saveDefaultConfig() + messages = mapOf( + "join" to "➡️ {player} joined the game.", + "quit" to "⬅️ {player} left the game.", + "death" to "💀 {player} {message}", + "advancement" to "🎉 {player} unlocked advancement: {advancement}" + ) + (config.getConfigurationSection("messages")?.getValues(false) as? Map ?: emptyMap()) + if (!setupNetwork()) { logger.severe("Failed to setup Solar Network Network, check your configuration.") } @@ -89,7 +97,7 @@ class SolarNetworkConnect : JavaPlugin() { } if (messageService != null && destinationChatId != null) { - server.pluginManager.registerEvents(SnChatListener(messageService!!, destinationChatId!!), this) + server.pluginManager.registerEvents(SnChatListener(messageService!!, destinationChatId!!, messages), this) } Bukkit.getPluginCommand("solar")!!.setExecutor(SnCommand(this.sn!!, this.economy)) @@ -105,6 +113,8 @@ class SolarNetworkConnect : JavaPlugin() { override fun onDisable() { logger.info(String.format("Disabled Version %s", description.version)); + sn?.disconnect() + webSocketJob?.cancel() } private fun setupNetwork(): Boolean { @@ -118,7 +128,7 @@ class SolarNetworkConnect : JavaPlugin() { destinationChatId = destination sn = SnService(baseUrl, clientId, clientSecret, botApiKey); messageService = SnMessageService(sn!!) - GlobalScope.launch { + webSocketJob = GlobalScope.launch { sn!!.connectWebSocketAsFlow().collect { packet -> handleWebSocketPacket(packet) } diff --git a/src/main/kotlin/dev/solsynth/snConnect/listeners/SnChatListener.kt b/src/main/kotlin/dev/solsynth/snConnect/listeners/SnChatListener.kt index b0b5376..8f01b77 100644 --- a/src/main/kotlin/dev/solsynth/snConnect/listeners/SnChatListener.kt +++ b/src/main/kotlin/dev/solsynth/snConnect/listeners/SnChatListener.kt @@ -1,13 +1,16 @@ package dev.solsynth.snConnect.listeners import dev.solsynth.snConnect.services.SnMessageService +import org.bukkit.entity.Player import org.bukkit.event.EventHandler 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 messageService: SnMessageService, private val destinationChatId: String) : Listener { +class SnChatListener(private val messageService: SnMessageService, private val destinationChatId: String, private val messages: Map) : Listener { @EventHandler fun onPlayerChat(event: AsyncPlayerChatEvent) { @@ -17,13 +20,30 @@ class SnChatListener(private val messageService: SnMessageService, private val d @EventHandler fun onPlayerJoin(event: PlayerJoinEvent) { - val message = "${event.player.name} joined the game." + val template = messages["join"] ?: "➡️ {player} joined the game." + val message = template.replace("{player}", event.player.name) messageService.sendMessage(destinationChatId, message) } @EventHandler fun onPlayerQuit(event: PlayerQuitEvent) { - val message = "${event.player.name} left the game." + val template = messages["quit"] ?: "⬅️ {player} left the game." + val message = template.replace("{player}", event.player.name) + messageService.sendMessage(destinationChatId, message) + } + + @EventHandler + fun onPlayerDeath(event: PlayerDeathEvent) { + val template = messages["death"] ?: "💀 {player} {message}" + val message = template.replace("{player}", event.entity.name).replace("{message}", event.deathMessage ?: "") + messageService.sendMessage(destinationChatId, message) + } + + @EventHandler + fun onPlayerAdvancement(event: PlayerAdvancementDoneEvent) { + val advancement = event.advancement.key.toString().substringAfter("minecraft:") + val template = messages["advancement"] ?: "🎉 {player} unlocked advancement: {advancement}" + val message = template.replace("{player}", event.player.name).replace("{advancement}", advancement) messageService.sendMessage(destinationChatId, message) } } diff --git a/src/main/kotlin/dev/solsynth/snConnect/services/SnService.kt b/src/main/kotlin/dev/solsynth/snConnect/services/SnService.kt index 4412a05..3dfd8f2 100644 --- a/src/main/kotlin/dev/solsynth/snConnect/services/SnService.kt +++ b/src/main/kotlin/dev/solsynth/snConnect/services/SnService.kt @@ -1,9 +1,11 @@ package dev.solsynth.snConnect.services import dev.solsynth.snConnect.models.WebSocketPacket -import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.launch import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.WebSocket @@ -15,6 +17,7 @@ import java.util.logging.Logger class SnService(private val baseUrl: String, val clientId: String, val clientSecret: String, val botApiKey: String?) { val client = OkHttpClient.Builder().build(); private val logger = Logger.getLogger(SnService::class.java.name) + private var websocket: WebSocket? = null fun getUrl(service: String, segment: String): String { return "$baseUrl/$service$segment" @@ -34,40 +37,60 @@ class SnService(private val baseUrl: String, val clientId: String, val clientSec return client.newWebSocket(request, listener) } - fun connectWebSocketAsFlow(): Flow = callbackFlow { - val url = "${getWsBaseUrl()}/ws"; + fun connectWebSocketAsFlow(): Flow = channelFlow { + val url = "${getWsBaseUrl()}/ws" val request = Request.Builder() .url(url) .apply { botApiKey?.let { header("Authorization", "Bearer $it") } } .build() - val websocket = client.newWebSocket(request, object : WebSocketListener() { - override fun onOpen(webSocket: WebSocket, response: Response) { - logger.info("WebSocket connection opened to $url") - } - override fun onMessage(webSocket: WebSocket, text: String) { - WebSocketPacket.fromJson(text)?.let { trySend(it) } - } + fun connect() { + websocket = client.newWebSocket(request, object : WebSocketListener() { + override fun onOpen(webSocket: WebSocket, response: Response) { + logger.info("WebSocket connection opened to $url") + } - override fun onMessage(webSocket: WebSocket, bytes: ByteString) { - val text = bytes.string(Charsets.UTF_8) - WebSocketPacket.fromJson(text)?.let { trySend(it) } - } + override fun onMessage(webSocket: WebSocket, text: String) { + WebSocketPacket.fromJson(text)?.let { trySend(it) } + } - override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { - logger.severe("WebSocket connection failed: ${t.message}, response: ${response?.code}") - close(t) - } + override fun onMessage(webSocket: WebSocket, bytes: ByteString) { + val text = bytes.string(Charsets.UTF_8) + WebSocketPacket.fromJson(text)?.let { trySend(it) } + } + + override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { + logger.severe("WebSocket connection failed: ${t.message}, response: ${response?.code}") + websocket = null + GlobalScope.launch { + delay(1000) + connect() + } + } + + override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { + logger.info("WebSocket connection closed: code=$code, reason=$reason") + websocket = null + GlobalScope.launch { + delay(1000) + connect() + } + } + }) + } + + connect() - override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { - logger.info("WebSocket connection closed: code=$code, reason=$reason") - close() - } - }) awaitClose { - websocket.close(1000, "Flow closed") + websocket?.close(1000, "Shutting down") + websocket = null } } + + fun disconnect() { + websocket?.close(1000, "Disconnecting") + websocket = null + } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 2b09b5d..9ea53a3 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -3,7 +3,11 @@ sn: client_id: goatcraft client_secret: 12345678 bot_secret: 114.514.19198 - destination_chat_id: some_chat_id chat: sync_rooms: [] outgoing_room: 00000000-0000-0000-0000-00000000008b +messages: + join: "➡️ {player} joined the game." + quit: "⬅️ {player} left the game." + death: "💀 {player} {message}" + advancement: "🎉 {player} unlocked advancement: {advancement}"