From 107489c3d2d9cec6107e6082f1fad53d39be080b Mon Sep 17 00:00:00 2001 From: ThatGravyBoat Date: Sat, 21 Sep 2024 20:16:20 -0230 Subject: [PATCH] RPC --- build.gradle.kts | 12 +++ .../gay/j10a1n15/sillygames/SillyGames.kt | 3 + .../{GameEventManager.kt => GameManager.kt} | 8 +- .../sillygames/games/wordle/Wordle.kt | 5 +- .../sillygames/games/wordle/WordleState.kt | 18 ++++- .../gay/j10a1n15/sillygames/rpc/RpcInfo.kt | 12 +++ .../gay/j10a1n15/sillygames/rpc/RpcManager.kt | 77 +++++++++++++++++++ .../j10a1n15/sillygames/rpc/RpcProvider.kt | 6 ++ .../j10a1n15/sillygames/screens/FullScreen.kt | 6 +- .../sillygames/screens/PictureInPicture.kt | 4 +- .../j10a1n15/sillygames/utils/Scheduling.kt | 29 +++++++ 11 files changed, 167 insertions(+), 13 deletions(-) rename src/main/kotlin/gay/j10a1n15/sillygames/games/{GameEventManager.kt => GameManager.kt} (69%) create mode 100644 src/main/kotlin/gay/j10a1n15/sillygames/rpc/RpcInfo.kt create mode 100644 src/main/kotlin/gay/j10a1n15/sillygames/rpc/RpcManager.kt create mode 100644 src/main/kotlin/gay/j10a1n15/sillygames/rpc/RpcProvider.kt create mode 100644 src/main/kotlin/gay/j10a1n15/sillygames/utils/Scheduling.kt diff --git a/build.gradle.kts b/build.gradle.kts index 844e5eb..c05221b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -27,6 +27,7 @@ repositories { maven("https://repo.sk1er.club/repository/maven-public/") maven("https://repo.sk1er.club/repository/maven-releases/") maven("https://repo.spongepowered.org/maven/") + maven("https://maven.teamresourceful.com/repository/maven-public/") } dependencies { @@ -39,7 +40,18 @@ dependencies { implementation(shade("org.spongepowered:mixin:0.7.11-SNAPSHOT") { isTransitive = false }) + implementation(shade("com.jagrosh:DiscordIPC:0.5.3") { + exclude(module = "log4j") + because("Different version conflicts with Minecraft's Log4J") + exclude(module = "gson") + because("Different version conflicts with Minecraft's Gson") + }) implementation(kotlin("stdlib")) +} +tasks { + fatJar { + relocate("com.jagrosh.discordipc", "gay.j10a1n15.sillygames.deps.discordipc") + } } diff --git a/src/main/kotlin/gay/j10a1n15/sillygames/SillyGames.kt b/src/main/kotlin/gay/j10a1n15/sillygames/SillyGames.kt index 214b224..f4c9753 100644 --- a/src/main/kotlin/gay/j10a1n15/sillygames/SillyGames.kt +++ b/src/main/kotlin/gay/j10a1n15/sillygames/SillyGames.kt @@ -2,6 +2,7 @@ package gay.j10a1n15.sillygames import gay.j10a1n15.sillygames.commands.CommandManager import gay.j10a1n15.sillygames.events.EventHandler +import gay.j10a1n15.sillygames.rpc.RpcManager import gay.j10a1n15.sillygames.screens.PictureInPicture import net.minecraftforge.common.MinecraftForge import net.minecraftforge.fml.common.Mod @@ -32,5 +33,7 @@ class SillyGames { ).forEach { MinecraftForge.EVENT_BUS.register(it) } + + RpcManager.start() } } diff --git a/src/main/kotlin/gay/j10a1n15/sillygames/games/GameEventManager.kt b/src/main/kotlin/gay/j10a1n15/sillygames/games/GameManager.kt similarity index 69% rename from src/main/kotlin/gay/j10a1n15/sillygames/games/GameEventManager.kt rename to src/main/kotlin/gay/j10a1n15/sillygames/games/GameManager.kt index 21ac674..8deb894 100644 --- a/src/main/kotlin/gay/j10a1n15/sillygames/games/GameEventManager.kt +++ b/src/main/kotlin/gay/j10a1n15/sillygames/games/GameManager.kt @@ -2,17 +2,13 @@ package gay.j10a1n15.sillygames.games import gay.j10a1n15.sillygames.events.Events -object GameEventManager { +object GameManager { - private var game: Game? = null + var game: Game? = null init { Events.TICK.register { game?.onTick() } Events.KEYBOARD.register { game?.onKeyHeld(it) } Events.KEYBOARD_DOWN.register { game?.onKeyPressed(it) } } - - fun setGame(game: Game?) { - this.game = game - } } diff --git a/src/main/kotlin/gay/j10a1n15/sillygames/games/wordle/Wordle.kt b/src/main/kotlin/gay/j10a1n15/sillygames/games/wordle/Wordle.kt index 3ed7fed..6c1fb02 100644 --- a/src/main/kotlin/gay/j10a1n15/sillygames/games/wordle/Wordle.kt +++ b/src/main/kotlin/gay/j10a1n15/sillygames/games/wordle/Wordle.kt @@ -1,6 +1,7 @@ package gay.j10a1n15.sillygames.games.wordle import gay.j10a1n15.sillygames.games.Game +import gay.j10a1n15.sillygames.rpc.RpcProvider import gg.essential.elementa.UIComponent import gg.essential.elementa.components.UIBlock import gg.essential.elementa.components.UIContainer @@ -34,7 +35,7 @@ private const val KEYBOARD_SPACING = 5 private const val ROWS = 6 private const val COLS = 5 -class Wordle : Game() { +class Wordle : Game(), RpcProvider { private val state: WordleState = WordleState() private var text: String = "" @@ -272,4 +273,6 @@ class Wordle : Game() { } override val name = "Wordle" + + override fun getRpcInfo() = this.state.getRpcInfo() } diff --git a/src/main/kotlin/gay/j10a1n15/sillygames/games/wordle/WordleState.kt b/src/main/kotlin/gay/j10a1n15/sillygames/games/wordle/WordleState.kt index dc8879c..ad58872 100644 --- a/src/main/kotlin/gay/j10a1n15/sillygames/games/wordle/WordleState.kt +++ b/src/main/kotlin/gay/j10a1n15/sillygames/games/wordle/WordleState.kt @@ -1,12 +1,14 @@ package gay.j10a1n15.sillygames.games.wordle +import gay.j10a1n15.sillygames.rpc.RpcInfo +import gay.j10a1n15.sillygames.rpc.RpcProvider import gay.j10a1n15.sillygames.utils.SillyUtils.getKeyCodeName import gay.j10a1n15.sillygames.utils.SillyUtils.replaceAt import gg.essential.elementa.utils.invisible import gg.essential.universal.UKeyboard import java.awt.Color -class WordleState(private val wordIndexInput: Int? = null) { +class WordleState(private val wordIndexInput: Int? = null): RpcProvider { private val letterKeys = setOf( UKeyboard.KEY_A, UKeyboard.KEY_B, UKeyboard.KEY_C, UKeyboard.KEY_D, UKeyboard.KEY_E, UKeyboard.KEY_F, @@ -24,6 +26,12 @@ class WordleState(private val wordIndexInput: Int? = null) { var tries = 0 var reset = false + private val rpc = RpcInfo( + firstLine = "Wordle", + secondLine = "Guesses: ${tries}/6", + start = System.currentTimeMillis() + ) + fun reset(wordIndex: Int? = null) { word = wordIndex?.let { WordleWordList.getWord(it) } ?: WordleWordList.getWord() this.wordIndex = wordIndex ?: WordleWordList.getIndex(word) @@ -32,6 +40,7 @@ class WordleState(private val wordIndexInput: Int? = null) { letters.clear() tries = 0 reset = false + updateRpc() } fun enterChar(char: Char) { @@ -92,6 +101,7 @@ class WordleState(private val wordIndexInput: Int? = null) { } tries++ + updateRpc() if (guess.lowercase() == word) { reset = true return "You won!\nWord Index: $wordIndex" @@ -104,6 +114,10 @@ class WordleState(private val wordIndexInput: Int? = null) { private fun isAllowed() = WordleWordList.isAllowed(guesses[tries]) + private fun updateRpc() { + rpc.secondLine = "Guesses: ${tries}/6" + } + fun getKeyboardColor(letter: Char): Color { if (!letters.containsKey(letter.lowercaseChar())) return WordlePalette.LIGHT_GRAY return when (letters[letter.lowercaseChar()]) { @@ -112,4 +126,6 @@ class WordleState(private val wordIndexInput: Int? = null) { else -> WordlePalette.GRAY } } + + override fun getRpcInfo() = rpc } diff --git a/src/main/kotlin/gay/j10a1n15/sillygames/rpc/RpcInfo.kt b/src/main/kotlin/gay/j10a1n15/sillygames/rpc/RpcInfo.kt new file mode 100644 index 0000000..e5d9ab7 --- /dev/null +++ b/src/main/kotlin/gay/j10a1n15/sillygames/rpc/RpcInfo.kt @@ -0,0 +1,12 @@ +package gay.j10a1n15.sillygames.rpc + +data class RpcInfo( + var firstLine: String, + var secondLine: String, + + var start: Long, + var end: Long? = null, + + var thumbnail: String? = null, + var icon: String? = null, +) diff --git a/src/main/kotlin/gay/j10a1n15/sillygames/rpc/RpcManager.kt b/src/main/kotlin/gay/j10a1n15/sillygames/rpc/RpcManager.kt new file mode 100644 index 0000000..cc11408 --- /dev/null +++ b/src/main/kotlin/gay/j10a1n15/sillygames/rpc/RpcManager.kt @@ -0,0 +1,77 @@ +package gay.j10a1n15.sillygames.rpc + +import com.google.gson.JsonObject +import com.jagrosh.discordipc.IPCClient +import com.jagrosh.discordipc.IPCListener +import com.jagrosh.discordipc.entities.RichPresence +import com.jagrosh.discordipc.entities.pipe.PipeStatus +import gay.j10a1n15.sillygames.games.GameManager +import gay.j10a1n15.sillygames.utils.Scheduling +import java.util.concurrent.CompletableFuture +import java.util.concurrent.ScheduledFuture +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds + +private const val CLIENT_ID = 1287128195124039715L + +object RpcManager : IPCListener { + + private var client: IPCClient? = null + private var lastInfo: RpcInfo? = null + private var scheduler: ScheduledFuture<*>? = null + private var started: Boolean = false + + fun start() { + if (started) return + CompletableFuture.runAsync { + client = IPCClient(CLIENT_ID) + client?.setListener(this) + client?.connect() + } + } + + fun stop() { + if (client?.status != PipeStatus.CONNECTED) return + CompletableFuture.runAsync { + close() + } + } + + override fun onReady(client: IPCClient?) { + scheduler = Scheduling.schedule(Duration.ZERO, 1.seconds) { + val info = (GameManager.game as? RpcProvider)?.getRpcInfo() + if (info != lastInfo) { + lastInfo = info?.copy() + updatePresence() + } + } + } + + override fun onClose(client: IPCClient?, json: JsonObject?) = close() + override fun onDisconnect(client: IPCClient?, t: Throwable?) = close() + + private fun close() { + scheduler?.cancel(true) + scheduler = null + started = false + client = null + } + + private fun updatePresence() { + val client = client ?: return + if (lastInfo == null) { + client.sendRichPresence(null) + } else { + val info = lastInfo!! + client.sendRichPresence(RichPresence.Builder().apply { + setDetails(info.firstLine) + setState(info.secondLine) + setStartTimestamp(info.start) + info.end?.let { setEndTimestamp(it) } + info.thumbnail?.let { setLargeImage(it) } + info.icon?.let { setSmallImage(it) } + }.build()) + } + } + +} diff --git a/src/main/kotlin/gay/j10a1n15/sillygames/rpc/RpcProvider.kt b/src/main/kotlin/gay/j10a1n15/sillygames/rpc/RpcProvider.kt new file mode 100644 index 0000000..aa30d63 --- /dev/null +++ b/src/main/kotlin/gay/j10a1n15/sillygames/rpc/RpcProvider.kt @@ -0,0 +1,6 @@ +package gay.j10a1n15.sillygames.rpc + +interface RpcProvider { + + fun getRpcInfo(): RpcInfo? +} diff --git a/src/main/kotlin/gay/j10a1n15/sillygames/screens/FullScreen.kt b/src/main/kotlin/gay/j10a1n15/sillygames/screens/FullScreen.kt index ed944d5..4521b44 100644 --- a/src/main/kotlin/gay/j10a1n15/sillygames/screens/FullScreen.kt +++ b/src/main/kotlin/gay/j10a1n15/sillygames/screens/FullScreen.kt @@ -1,7 +1,7 @@ package gay.j10a1n15.sillygames.screens import gay.j10a1n15.sillygames.games.Game -import gay.j10a1n15.sillygames.games.GameEventManager +import gay.j10a1n15.sillygames.games.GameManager import gg.essential.elementa.ElementaVersion import gg.essential.elementa.WindowScreen import gg.essential.elementa.components.UIBlock @@ -20,7 +20,7 @@ class FullScreen(private val element: Game) : WindowScreen( ) { init { - GameEventManager.setGame(element) + GameManager.game = element } private val container = UIBlock().constrain { @@ -50,7 +50,7 @@ class FullScreen(private val element: Game) : WindowScreen( override fun onScreenClose() { super.onScreenClose() - GameEventManager.setGame(null) + GameManager.game = null } override fun updateGuiScale() { diff --git a/src/main/kotlin/gay/j10a1n15/sillygames/screens/PictureInPicture.kt b/src/main/kotlin/gay/j10a1n15/sillygames/screens/PictureInPicture.kt index 6b03c0c..0dc4e2a 100644 --- a/src/main/kotlin/gay/j10a1n15/sillygames/screens/PictureInPicture.kt +++ b/src/main/kotlin/gay/j10a1n15/sillygames/screens/PictureInPicture.kt @@ -2,7 +2,7 @@ package gay.j10a1n15.sillygames.screens import gay.j10a1n15.sillygames.events.Events import gay.j10a1n15.sillygames.games.Game -import gay.j10a1n15.sillygames.games.GameEventManager +import gay.j10a1n15.sillygames.games.GameManager import gay.j10a1n15.sillygames.games.Snake import gg.essential.elementa.ElementaVersion import gg.essential.elementa.WindowScreen @@ -54,6 +54,6 @@ object PictureInPicture : WindowScreen( } private fun updateEventListeners() { - GameEventManager.setGame(if (visible) game else null) + GameManager.game = if (visible) game else null } } diff --git a/src/main/kotlin/gay/j10a1n15/sillygames/utils/Scheduling.kt b/src/main/kotlin/gay/j10a1n15/sillygames/utils/Scheduling.kt new file mode 100644 index 0000000..23999c3 --- /dev/null +++ b/src/main/kotlin/gay/j10a1n15/sillygames/utils/Scheduling.kt @@ -0,0 +1,29 @@ +package gay.j10a1n15.sillygames.utils + +import java.util.concurrent.Executors +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.ScheduledFuture +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger +import kotlin.time.Duration +import kotlin.time.DurationUnit + +object Scheduling { + + private val counter = AtomicInteger(0) + private val scheduler: ScheduledExecutorService = Executors.newScheduledThreadPool(10) { target: Runnable? -> + Thread(target, "Scheduling-Thread-${counter.getAndIncrement()}") + } + + fun schedule(time: Duration, runnable: () -> Unit): ScheduledFuture<*> = scheduler.schedule( + runnable, + time.toLong(DurationUnit.MILLISECONDS), + TimeUnit.MILLISECONDS, + ) + + fun schedule(initalDelay: Duration, delay: Duration, runnable: () -> Unit): ScheduledFuture<*> = scheduler.scheduleAtFixedRate( + runnable, + initalDelay.toLong(DurationUnit.MILLISECONDS), + delay.toLong(DurationUnit.MILLISECONDS), TimeUnit.MILLISECONDS, + ) +}