diff --git a/build.gradle.kts b/build.gradle.kts index f92735998..0e105b817 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -40,6 +40,9 @@ allprojects { implementation(kotlin("reflect")) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0-RC") + implementation("com.squareup.okhttp3:okhttp:4.9.3") + implementation("com.squareup.okio:okio:3.4.0") + compileOnly("com.github.NotEnoughUpdates:NotEnoughUpdates:2.4.0:all") annotationProcessor("org.spongepowered:mixin:0.8.5-SNAPSHOT") diff --git a/src/main/kotlin/me/odinmain/OdinMain.kt b/src/main/kotlin/me/odinmain/OdinMain.kt index 4e494cdb1..398891158 100644 --- a/src/main/kotlin/me/odinmain/OdinMain.kt +++ b/src/main/kotlin/me/odinmain/OdinMain.kt @@ -1,5 +1,7 @@ package me.odinmain +import com.google.gson.Gson +import com.google.gson.GsonBuilder import kotlinx.coroutines.* import me.odinmain.commands.CommandRegistry import me.odinmain.config.Config @@ -12,11 +14,12 @@ import me.odinmain.features.impl.render.WaypointManager import me.odinmain.utils.ServerUtils import me.odinmain.utils.SplitsManager import me.odinmain.utils.clock.Executor +import me.odinmain.utils.network.WebUtils.createClient +import me.odinmain.utils.network.WebUtils.postData import me.odinmain.utils.render.HighlightRenderer import me.odinmain.utils.render.RenderUtils import me.odinmain.utils.render.RenderUtils2D import me.odinmain.utils.render.Renderer -import me.odinmain.utils.sendDataToServer import me.odinmain.utils.skyblock.* import me.odinmain.utils.skyblock.dungeon.DungeonListener import me.odinmain.utils.skyblock.dungeon.DungeonUtils @@ -37,6 +40,9 @@ object OdinMain { val scope = CoroutineScope(SupervisorJob() + EmptyCoroutineContext) val logger: Logger = LogManager.getLogger("Odin") + val okClient = createClient() + val gson: Gson = GsonBuilder().setPrettyPrinting().create() + var display: GuiScreen? = null inline val isLegitVersion: Boolean get() = Loader.instance().activeModList.none { it.modId == "odclient" } @@ -70,7 +76,7 @@ object OdinMain { scope.launch(Dispatchers.IO) { DungeonWaypointConfig.loadConfig() ClickGUIModule.latestVersionNumber = ClickGUIModule.checkNewerVersion(VERSION) - sendDataToServer(body = """{"username": "$name", "version": "${if (isLegitVersion) "legit" else "cheater"} $VERSION"}""") + postData("https://api.odtheking.com/tele/", """{"username": "$name", "version": "${if (isLegitVersion) "legit" else "cheater"} $VERSION"}""") } } diff --git a/src/main/kotlin/me/odinmain/commands/impl/DevCommand.kt b/src/main/kotlin/me/odinmain/commands/impl/DevCommand.kt index 28cf61343..b93b71b58 100644 --- a/src/main/kotlin/me/odinmain/commands/impl/DevCommand.kt +++ b/src/main/kotlin/me/odinmain/commands/impl/DevCommand.kt @@ -6,6 +6,7 @@ import kotlinx.coroutines.launch import me.odinmain.OdinMain.VERSION import me.odinmain.OdinMain.mc import me.odinmain.OdinMain.scope +import me.odinmain.config.Config import me.odinmain.events.impl.PacketEvent import me.odinmain.features.ModuleManager.generateFeatureList import me.odinmain.features.impl.dungeon.MapInfo @@ -14,11 +15,17 @@ import me.odinmain.features.impl.floor7.DragonPriority.findPriority import me.odinmain.features.impl.floor7.WitherDragonState import me.odinmain.features.impl.floor7.WitherDragons.priorityDragon import me.odinmain.features.impl.floor7.WitherDragonsEnum +import me.odinmain.features.impl.floor7.p3.MelodyMessage.webSocket +import me.odinmain.features.impl.floor7.p3.TerminalSolver.firstClickProt import me.odinmain.features.impl.nether.NoPre +import me.odinmain.features.impl.render.ClickGUIModule.wsServer import me.odinmain.features.impl.render.PlayerSize +import me.odinmain.features.impl.render.PlayerSize.DEV_SERVER +import me.odinmain.features.impl.render.PlayerSize.buildDevBody import me.odinmain.utils.isOtherPlayer +import me.odinmain.utils.network.WebUtils.postData import me.odinmain.utils.postAndCatch -import me.odinmain.utils.sendDataToServer +import me.odinmain.utils.render.Colors import me.odinmain.utils.skyblock.* import me.odinmain.utils.skyblock.PlayerUtils.posX import me.odinmain.utils.skyblock.PlayerUtils.posZ @@ -33,6 +40,17 @@ import net.minecraft.util.ChatComponentText val devCommand = Commodore("oddev") { + literal("firstclickprot").runs { time: Long -> + firstClickProt = time + Config.save() + } + + literal("ws") { + literal("connect").runs { lobby: String -> + webSocket.connect("${wsServer}$lobby") + } + } + literal("drags") { runs { text: GreedyString -> val drags = WitherDragonsEnum.entries.mapNotNull { @@ -86,27 +104,18 @@ val devCommand = Commodore("oddev") { } literal("updatedevs").runs { - PlayerSize.updateCustomProperties() - } - - literal("adddev").runs { name: String, password: String, xSize: Float?, ySize: Float?, zSize: Float? -> - val x = xSize ?: 0.6 - val y = ySize ?: 0.6 - val z = zSize ?: 0.6 - modMessage("Sending data... name: $name, password: $password") scope.launch { - modMessage(sendDataToServer("$name, [1,2,3], [$x,$y,$z], false, , $password", "https://tj4yzotqjuanubvfcrfo7h5qlq0opcyk.lambda-url.eu-north-1.on.aws/")) + PlayerSize.updateCustomProperties() } } - literal("customSize").runs { password: String, xSize: Float?, ySize: Float?, zSize: Float?, customName: String? -> - val x = xSize ?: 0.6 - val y = ySize ?: 0.6 - val z = zSize ?: 0.6 - val name = customName ?: "" + literal("adddev").runs { name: String, password: String, xSize: Float?, ySize: Float?, zSize: Float? -> + val x = xSize ?: 0.6f + val y = ySize ?: 0.6f + val z = zSize ?: 0.6f + modMessage("Sending data... name: $name, x: $x, y: $y, z: $z") scope.launch { - modMessage(sendDataToServer(body = "${mc.thePlayer.name}, [1,2,3], [$x,$y,$z], false, $name, $password", "https://tj4yzotqjuanubvfcrfo7h5qlq0opcyk.lambda-url.eu-north-1.on.aws/")) - PlayerSize.updateCustomProperties() + modMessage(postData(DEV_SERVER, buildDevBody(name, Colors.WHITE, x, y, z, false, " ", password)).getOrNull()) } } diff --git a/src/main/kotlin/me/odinmain/commands/impl/SoopyCommand.kt b/src/main/kotlin/me/odinmain/commands/impl/SoopyCommand.kt index df15a3852..338b3fa24 100644 --- a/src/main/kotlin/me/odinmain/commands/impl/SoopyCommand.kt +++ b/src/main/kotlin/me/odinmain/commands/impl/SoopyCommand.kt @@ -2,12 +2,10 @@ package me.odinmain.commands.impl import com.github.stivais.commodore.Commodore import com.github.stivais.commodore.utils.SyntaxException -import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.launch -import kotlinx.coroutines.withTimeout import me.odinmain.OdinMain.mc import me.odinmain.OdinMain.scope -import me.odinmain.utils.fetchURLData +import me.odinmain.utils.network.WebUtils.fetchString import me.odinmain.utils.skyblock.modMessage val soopyCommand = Commodore("soopycmd", "spcmd", "spc") { @@ -33,15 +31,10 @@ val soopyCommand = Commodore("soopycmd", "spcmd", "spc") { val player = user ?: mc.thePlayer.name modMessage("Running command...") scope.launch { - try { - modMessage(withTimeout(5000) { - fetchURLData("https://soopy.dev/api/soopyv2/botcommand?m=$command&u=$player") } - ) - } catch (_: TimeoutCancellationException) { - modMessage("Request timed out") - } catch (e: Exception) { - modMessage("Failed to fetch data: ${e.message}") - } + fetchString("https://soopy.dev/api/soopyv2/botcommand?m=$command&u=$player").fold( + { modMessage(it) }, + { e -> modMessage("Failed to fetch data: ${e.message}") } + ) } } } diff --git a/src/main/kotlin/me/odinmain/features/impl/dungeon/Mimic.kt b/src/main/kotlin/me/odinmain/features/impl/dungeon/Mimic.kt index b0bd3829a..d0cdc085b 100644 --- a/src/main/kotlin/me/odinmain/features/impl/dungeon/Mimic.kt +++ b/src/main/kotlin/me/odinmain/features/impl/dungeon/Mimic.kt @@ -30,8 +30,8 @@ object Mimic : Module( private val color by ColorSetting("Color", Colors.MINECRAFT_RED.withAlpha(0.5f), allowAlpha = true, desc = "The color of the box.").withDependency { mimicBox } private val lineWidth by NumberSetting("Line Width", 2f, 0.1f, 10f, 0.1f, desc = "The width of the box's lines.").withDependency { mimicBox } - private val princeMessageToggle by BooleanSetting("Toggle Prince Message", false, desc = "Toggles the prince killed message.") - val princeMessage by StringSetting("Prince Message", "Prince Killed!", 128, desc = "Message sent when prince is detected as killed.").withDependency { princeMessageToggle } + private val princeMessageToggle by BooleanSetting("Prince Message", true, desc = "Toggles the prince killed message.") + val princeMessage by StringSetting("Prince Message Text", "Prince Killed!", 128, desc = "Message sent when prince is detected as killed.").withDependency { princeMessageToggle } private val princeReset by ActionSetting("Prince Killed", desc = "Sends Prince killed message in party chat.") { princeKilled() } private const val MIMIC_TEXTURE = "ewogICJ0aW1lc3RhbXAiIDogMTY3Mjc2NTM1NTU0MCwKICAicHJvZmlsZUlkIiA6ICJhNWVmNzE3YWI0MjA0MTQ4ODlhOTI5ZDA5OTA0MzcwMyIsCiAgInByb2ZpbGVOYW1lIiA6ICJXaW5zdHJlYWtlcnoiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTE5YzEyNTQzYmM3NzkyNjA1ZWY2OGUxZjg3NDlhZThmMmEzODFkOTA4NWQ0ZDRiNzgwYmExMjgyZDM1OTdhMCIsCiAgICAgICJtZXRhZGF0YSIgOiB7CiAgICAgICAgIm1vZGVsIiA6ICJzbGltIgogICAgICB9CiAgICB9CiAgfQp9" diff --git a/src/main/kotlin/me/odinmain/features/impl/floor7/WitherDragonEnum.kt b/src/main/kotlin/me/odinmain/features/impl/floor7/WitherDragonEnum.kt index f8155cbc9..d309b6185 100644 --- a/src/main/kotlin/me/odinmain/features/impl/floor7/WitherDragonEnum.kt +++ b/src/main/kotlin/me/odinmain/features/impl/floor7/WitherDragonEnum.kt @@ -78,12 +78,12 @@ enum class WitherDragonsEnum ( } } - fun setDead() { + fun setDead(deathless: Boolean = false) { state = WitherDragonState.DEAD dragonEntityList.remove(entity) entityId = null entity = null - lastDragonDeath = this + if (!deathless) lastDragonDeath = this if (sendArrowHit && WitherDragons.enabled && currentTick - spawnedTime < skipKillTime) modMessage("§fArrows Hit on §${colorCode}${name}§7: ${arrowsHit.entries.joinToString(", ") { "§f${it.key}§7: §6${it.value.good}${it.value.late.let { if (it > 0) " §8(§7${it}§8)" else "" }}§7" }}.") if (priorityDragon == this) priorityDragon = None diff --git a/src/main/kotlin/me/odinmain/features/impl/floor7/WitherDragons.kt b/src/main/kotlin/me/odinmain/features/impl/floor7/WitherDragons.kt index 39c1da941..33ebe8a6f 100644 --- a/src/main/kotlin/me/odinmain/features/impl/floor7/WitherDragons.kt +++ b/src/main/kotlin/me/odinmain/features/impl/floor7/WitherDragons.kt @@ -139,9 +139,8 @@ object WitherDragons : Module( onMessage(Regex("^\\[BOSS] Wither King: (Oh, this one hurts!|I have more of those\\.|My soul is disposable\\.)$"), { enabled && DungeonUtils.getF7Phase() == M7Phases.P5 } ) { WitherDragonsEnum.entries.find { lastDragonDeath == it && lastDragonDeath != WitherDragonsEnum.None }?.let { if (sendNotification) modMessage("§${it.colorCode}${it.name} dragon counts.") - it.state = WitherDragonState.DEAD - } - WitherDragonsEnum.entries.find { it.state == WitherDragonState.ALIVE }?.let { it.state = WitherDragonState.DEAD } + lastDragonDeath = WitherDragonsEnum.None + } ?: WitherDragonsEnum.entries.find { it.state == WitherDragonState.ALIVE }?.setDead(true) } } diff --git a/src/main/kotlin/me/odinmain/features/impl/floor7/p3/MelodyMessage.kt b/src/main/kotlin/me/odinmain/features/impl/floor7/p3/MelodyMessage.kt index 1edbad600..098b7978c 100644 --- a/src/main/kotlin/me/odinmain/features/impl/floor7/p3/MelodyMessage.kt +++ b/src/main/kotlin/me/odinmain/features/impl/floor7/p3/MelodyMessage.kt @@ -1,16 +1,25 @@ package me.odinmain.features.impl.floor7.p3 +import me.odinmain.OdinMain.gson import me.odinmain.clickgui.settings.Setting.Companion.withDependency import me.odinmain.clickgui.settings.impl.BooleanSetting import me.odinmain.clickgui.settings.impl.StringSetting import me.odinmain.events.impl.TerminalEvent import me.odinmain.features.Module import me.odinmain.features.impl.floor7.p3.termsim.TermSimGUI +import me.odinmain.features.impl.render.ClickGUIModule.wsServer +import me.odinmain.utils.network.webSocket +import me.odinmain.utils.render.RenderUtils +import me.odinmain.utils.skyblock.LocationUtils import me.odinmain.utils.skyblock.dungeon.DungeonUtils import me.odinmain.utils.skyblock.dungeon.M7Phases import me.odinmain.utils.skyblock.partyMessage import me.odinmain.utils.skyblock.sendCommand +import me.odinmain.utils.ui.getTextWidth +import net.minecraft.item.Item +import net.minecraft.network.play.server.S2FPacketSetSlot import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import java.util.concurrent.ConcurrentHashMap object MelodyMessage : Module( name = "Melody Message", @@ -20,26 +29,131 @@ object MelodyMessage : Module( private val melodyMessage by StringSetting("Melody Message", "Melody Terminal start!", 128, desc = "Message sent when the melody terminal opens.").withDependency { sendMelodyMessage } private val melodyProgress by BooleanSetting("Melody Progress", false, desc = "Tells the party about melody terminal progress.") private val melodySendCoords by BooleanSetting("Melody Send Coords", false, desc = "Sends the coordinates of the melody terminal.").withDependency { melodyProgress } + private val broadcast by BooleanSetting("Broadcast Progress", true, desc = "Broadcasts melody progress to all other odin users in your run using a websocket.") + private val melodyGui by HUD("Progress GUI", "Shows a GUI with the progress of broadcasting odin users in the melody terminal.", true) { + if (it) { + drawMelody(MelodyData(3, 1, 2), 0) + return@HUD 45f to 25f + } + + if (!broadcast || !webSocket.connected) return@HUD 0f to 0f + melodies.entries.forEachIndexed { i, (name, data) -> + if (!showOwn && name == mc.session.username) return@forEachIndexed + drawMelody(data, i) + } + 45f to 25f + }.withDependency { broadcast } + + // explicit boolean because showOwn is broken or something + private val showOwn: Boolean by BooleanSetting("Show Own", false, desc = "Shows your own melody progress in the GUI.").withDependency { melodyGui.enabled && broadcast } + + val webSocket = webSocket { + onMessage { + val (user, type, slot) = try { gson.fromJson(it, UpdateMessage::class.java) } catch (_: Exception) { return@onMessage } + val entry = melodies.getOrPut(user) { MelodyData(null, null, null) } + when (type) { + 0 -> melodies.remove(user) + 1 -> entry.clay = slot + 2 -> entry.purple = slot + 5 -> entry.pane = slot + } + } + } + + private val melodies = ConcurrentHashMap() + private val lastSent = MelodyData(null, null, null) + + init { + onMessage(Regex("^\\[BOSS] Goldor: Who dares trespass into my domain\\?$"), { enabled && broadcast }) { + webSocket.connect("${wsServer}${LocationUtils.lobbyId}") + } + + onMessage(Regex("^The Core entrance is opening!$"), { enabled && broadcast }) { + webSocket.shutdown() + melodies.clear() + } + + onWorldLoad { + webSocket.shutdown() + melodies.clear() + } + + onPacket({ enabled && broadcast }) { + val term = TerminalSolver.currentTerm ?: return@onPacket + if (DungeonUtils.getF7Phase() != M7Phases.P3 || term.type != TerminalTypes.MELODY || it.func_149173_d() !in 0 until term.type.windowSize || mc.currentScreen is TermSimGUI) return@onPacket + + val meta = it.func_149174_e()?.metadata ?: return@onPacket - private var claySlots = hashMapOf(25 to "Melody 25%", 34 to "Melody 50%", 43 to "Melody 75%") + val clay = Item.getIdFromItem(it.func_149174_e().item) == 159 + if (clay && meta != 5) return@onPacket + + if (clay) { + val position = it.func_149173_d() / 9 + if (lastSent.clay == position) return@onPacket + webSocket.send(update(1, position)) + lastSent.clay = position + if (melodyProgress) clayProgress[position]?.let { partyMessage(it) } + return@onPacket + } + + if (meta != 5 && meta != 2) return@onPacket + + val index = mapToRange(it.func_149173_d()) ?: return@onPacket + + val shouldSend = when (meta) { + 2 -> lastSent.purple != index + 5 -> lastSent.pane != index + else -> false + } + + if (!shouldSend) return@onPacket + webSocket.send(update(meta, index)) + when (meta) { + 2 -> lastSent.purple = index + 5 -> lastSent.pane = index + } + } + } + + @SubscribeEvent + fun onClose(event: TerminalEvent.Closed) { + if (event.terminal.type != TerminalTypes.MELODY) return + webSocket.send(update(0, 0)) + } + + private val clayProgress = hashMapOf(1 to "Melody 25%", 2 to "Melody 50%", 3 to "Melody 75%") @SubscribeEvent fun onTermLoad(event: TerminalEvent.Opened) { if (DungeonUtils.getF7Phase() != M7Phases.P3 || event.terminal.type != TerminalTypes.MELODY || mc.currentScreen is TermSimGUI) return if (sendMelodyMessage) partyMessage(melodyMessage) if (melodySendCoords) sendCommand("od sendcoords", true) + } + + fun update(type: Int, slot: Int): String = gson.toJson(UpdateMessage(mc.session.username, type, slot)) - claySlots = hashMapOf(25 to "Melody 25%", 34 to "Melody 50%", 43 to "Melody 75%") + val ranges = listOf(1..5, 10..14, 19..23, 28..32, 37..41) + + fun mapToRange(value: Int): Int? { + for (r in ranges) { + if (value in r) return (value - r.first) % 5 + } + return null } - init { - execute(250) { - if (DungeonUtils.getF7Phase() != M7Phases.P3 || TerminalSolver.currentTerm?.type != TerminalTypes.MELODY || mc.currentScreen is TermSimGUI || !melodyProgress) return@execute + private val width = getTextWidth("§d■").toFloat() - val greenClayIndices = claySlots.keys.filter { index -> TerminalSolver.currentTerm?.items?.get(index)?.metadata == 5 }.ifEmpty { return@execute } + fun drawMelody(data: MelodyData, index: Int) { + val y = width * 2 * index - partyMessage(claySlots[greenClayIndices.last()] ?: return@execute) - greenClayIndices.forEach { claySlots.remove(it) } + repeat(5) { + if (data.purple == it) RenderUtils.drawText("§d■", width * it, y) + val color = if (data.pane == it) "§a" else "§f" + RenderUtils.drawText("${color}■", width * it, y + width) } + data.clay?.let { RenderUtils.drawText(it.toString(), 40f, y + width / 2) } } + + data class UpdateMessage(val user: String, val type: Int, val slot: Int) + data class MelodyData(var purple: Int?, var pane: Int?, var clay: Int?) } \ No newline at end of file diff --git a/src/main/kotlin/me/odinmain/features/impl/floor7/p3/TerminalSolver.kt b/src/main/kotlin/me/odinmain/features/impl/floor7/p3/TerminalSolver.kt index 5fe8b6c5e..1a0ef2afc 100644 --- a/src/main/kotlin/me/odinmain/features/impl/floor7/p3/TerminalSolver.kt +++ b/src/main/kotlin/me/odinmain/features/impl/floor7/p3/TerminalSolver.kt @@ -88,6 +88,8 @@ object TerminalSolver : Module( val melodyRowColor by ColorSetting("Melody Row", Colors.MINECRAFT_RED, true, desc = "Color of the row indicator for melody.").withDependency { showColors && !cancelMelodySolver } val melodyPointerColor by ColorSetting("Melody Pointer", Colors.MINECRAFT_GREEN, true, desc = "Color of the location for pressing for melody.").withDependency { showColors && !cancelMelodySolver } + var firstClickProt by NumberSetting("First Click Protection", 350L, 0, 500, 10, unit = "ms", desc = "The amount of time before you can click in a terminal.").hide() + var currentTerm: TerminalHandler? = null private set var lastTermOpened: TerminalHandler? = null diff --git a/src/main/kotlin/me/odinmain/features/impl/floor7/p3/termGUI/CustomTermGui.kt b/src/main/kotlin/me/odinmain/features/impl/floor7/p3/termGUI/CustomTermGui.kt index 56f452bed..ebbf49f00 100644 --- a/src/main/kotlin/me/odinmain/features/impl/floor7/p3/termGUI/CustomTermGui.kt +++ b/src/main/kotlin/me/odinmain/features/impl/floor7/p3/termGUI/CustomTermGui.kt @@ -3,6 +3,7 @@ package me.odinmain.features.impl.floor7.p3.termGUI import me.odinmain.OdinMain.mc import me.odinmain.events.impl.GuiEvent import me.odinmain.features.impl.floor7.p3.TerminalSolver +import me.odinmain.features.impl.floor7.p3.TerminalSolver.firstClickProt import me.odinmain.features.impl.floor7.p3.TerminalSolver.hideClicked import me.odinmain.utils.postAndCatch import me.odinmain.utils.render.Color @@ -51,7 +52,7 @@ abstract class TermGui { fun mouseClicked(button: Int) { getHoveredItem()?.let { slot -> TerminalSolver.currentTerm?.let { - if (System.currentTimeMillis() - it.timeOpened >= 350 && !GuiEvent.CustomTermGuiClick(slot, button).postAndCatch() && it.canClick(slot, button)) { + if (System.currentTimeMillis() - it.timeOpened >= firstClickProt && !GuiEvent.CustomTermGuiClick(slot, button).postAndCatch() && it.canClick(slot, button)) { it.click(slot, if (button == 0) ClickType.Middle else ClickType.Right, hideClicked && !it.isClicked) if (TerminalSolver.customAnimations) colorAnimations[slot]?.start() } diff --git a/src/main/kotlin/me/odinmain/features/impl/render/ClickGUIModule.kt b/src/main/kotlin/me/odinmain/features/impl/render/ClickGUIModule.kt index ace6edd56..fbab69048 100644 --- a/src/main/kotlin/me/odinmain/features/impl/render/ClickGUIModule.kt +++ b/src/main/kotlin/me/odinmain/features/impl/render/ClickGUIModule.kt @@ -1,15 +1,16 @@ package me.odinmain.features.impl.render -import com.google.gson.JsonParser +import com.google.gson.annotations.SerializedName import me.odinmain.OdinMain import me.odinmain.clickgui.ClickGUI import me.odinmain.clickgui.HudManager import me.odinmain.clickgui.settings.AlwaysActive +import me.odinmain.clickgui.settings.Setting.Companion.withDependency import me.odinmain.clickgui.settings.impl.* import me.odinmain.config.Config import me.odinmain.features.Category import me.odinmain.features.Module -import me.odinmain.utils.fetchURLData +import me.odinmain.utils.network.WebUtils.fetchJson import me.odinmain.utils.render.Color import me.odinmain.utils.skyblock.* import net.minecraft.event.ClickEvent @@ -25,6 +26,10 @@ object ClickGUIModule: Module( val clickGUIColor by ColorSetting("Gui Color", Color(50, 150, 220), allowAlpha = false, desc = "Color theme in the gui.") val hudChat by BooleanSetting("Show HUDs in GUIs", true, desc = "Shows HUDs in GUIs.") + val apiSettings by DropdownSetting("Api Settings") + // val apiServer by StringSetting("Api Server", "https://api.odtheking.com/", 128, desc = "The server to fetch data from. Only change this if you know what you're doing").withDependency { apiSettings } + val wsServer by StringSetting("WebSocket Server", "wss://api.odtheking.com/ws/", 128, desc = "The websocket server to connect to. Only change this if you know what you're doing.").withDependency { apiSettings } + private val action by ActionSetting("Open Example Hud", desc = "Opens an example hud to allow configuration of huds.") { OdinMain.display = HudManager } @@ -99,12 +104,10 @@ object ClickGUIModule: Module( resetPositions() } - fun checkNewerVersion(currentVersion: String): String? { - val newestVersion = try { - JsonParser().parse(fetchURLData("https://api.github.com/repos/odtheking/Odin/releases/latest")).asJsonObject - } catch (e: Exception) { return null } + suspend fun checkNewerVersion(currentVersion: String): String? { + val newest = fetchJson("https://api.github.com/repos/odtheking/Odin/releases/latest").getOrElse { return null } - if (isSecondNewer(currentVersion, newestVersion.get("tag_name").asString)) return newestVersion.get("tag_name").asString.toString().replace("\"", "") + if (isSecondNewer(currentVersion, newest.tagName)) return newest.tagName.replace("\"", "") return null } @@ -125,8 +128,6 @@ object ClickGUIModule: Module( } } - - override fun onKeybind() { this.toggle() } @@ -136,4 +137,9 @@ object ClickGUIModule: Module( super.onEnable() toggle() } + + data class Release( + @SerializedName("tag_name") + val tagName: String + ) } \ No newline at end of file diff --git a/src/main/kotlin/me/odinmain/features/impl/render/PlayerSize.kt b/src/main/kotlin/me/odinmain/features/impl/render/PlayerSize.kt index ae63fe1bc..c34e8448b 100644 --- a/src/main/kotlin/me/odinmain/features/impl/render/PlayerSize.kt +++ b/src/main/kotlin/me/odinmain/features/impl/render/PlayerSize.kt @@ -1,16 +1,16 @@ package me.odinmain.features.impl.render -import com.google.gson.JsonParser +import com.google.gson.annotations.SerializedName import kotlinx.coroutines.launch import me.odinmain.OdinMain import me.odinmain.clickgui.settings.AlwaysActive import me.odinmain.clickgui.settings.Setting.Companion.withDependency import me.odinmain.clickgui.settings.impl.* import me.odinmain.features.Module -import me.odinmain.utils.fetchData +import me.odinmain.utils.network.WebUtils.fetchJson +import me.odinmain.utils.network.WebUtils.postData import me.odinmain.utils.render.Color import me.odinmain.utils.render.Colors -import me.odinmain.utils.sendDataToServer import me.odinmain.utils.skyblock.modMessage import net.minecraft.client.entity.AbstractClientPlayer import net.minecraft.client.model.ModelBase @@ -37,10 +37,18 @@ object PlayerSize : Module( private var showHidden by DropdownSetting("Show Hidden", false).withDependency { isRandom } private val passcode by StringSetting("Passcode", "odin", desc = "Passcode for dev features.").withDependency { showHidden && isRandom } + const val DEV_SERVER = "https://api.odtheking.com/devs/" + private val sendDevData by ActionSetting("Send Dev Data", desc = "Sends dev data to the server.") { showHidden = false OdinMain.scope.launch { - modMessage(sendDataToServer(body = "${mc.thePlayer.name}, [${devWingsColor.red},${devWingsColor.green},${devWingsColor.blue}], [$devSizeX,$devSizeY,$devSizeZ], $devWings, , $passcode", "https://tj4yzotqjuanubvfcrfo7h5qlq0opcyk.lambda-url.eu-north-1.on.aws/")) + val body = buildDevBody( + mc.thePlayer?.name ?: return@launch, + devWingsColor, devSizeX, devSizeY, + devSizeZ, devWings, " ", passcode + ) + + modMessage(postData(DEV_SERVER, body).getOrNull()) updateCustomProperties() } }.withDependency { isRandom } @@ -48,7 +56,14 @@ object PlayerSize : Module( private var randoms: HashMap = HashMap() val isRandom get() = randoms.containsKey(mc.session?.username) - data class RandomPlayer(val scale: Triple, val wings: Boolean = false, val wingsColor: Color = Colors.WHITE, val customName: String, val isDev: Boolean) + data class RandomPlayer( + @SerializedName("CustomName") val customName: String?, + @SerializedName("DevName") val name: String, + @SerializedName("IsDev") val isDev: Boolean?, + @SerializedName("WingsColor") val wingsColor: Array, + @SerializedName("Size") val scale: Array, + @SerializedName("Wings") val wings: Boolean + ) @JvmStatic fun preRenderCallbackScaleHook(entityLivingBaseIn: AbstractClientPlayer) { @@ -59,24 +74,24 @@ object PlayerSize : Module( if (!randoms.containsKey(entityLivingBaseIn.name)) return if (!devSize && entityLivingBaseIn.name == mc.thePlayer.name) return val random = randoms[entityLivingBaseIn.name] ?: return - if (random.scale.second < 0) GlStateManager.translate(0f, random.scale.second * 2, 0f) - GlStateManager.scale(random.scale.first, random.scale.second, random.scale.third) + if (random.scale[1] < 0) GlStateManager.translate(0f, random.scale[1] * 2, 0f) + GlStateManager.scale(random.scale[0], random.scale[1], random.scale[2]) } - private val pattern = Regex("Decimal\\('(-?\\d+(?:\\.\\d+)?)'\\)") - - fun updateCustomProperties() { - val data = fetchData("https://tj4yzotqjuanubvfcrfo7h5qlq0opcyk.lambda-url.eu-north-1.on.aws/").replace(pattern) { match -> match.groupValues[1] }.ifEmpty { null } ?: return - JsonParser().parse(data)?.asJsonArray?.forEach { - val jsonElement = it.asJsonObject - val randomsName = jsonElement.get("DevName")?.asString ?: return@forEach - val size = jsonElement.get("Size")?.asJsonArray?.let { sizeArray -> Triple(sizeArray[0].asFloat, sizeArray[1].asFloat, sizeArray[2].asFloat) } ?: return@forEach - val wings = jsonElement.get("Wings")?.asBoolean == true - val wingsColor = jsonElement.get("WingsColor")?.asJsonArray?.let { colorArray -> Color(colorArray[0].asInt, colorArray[1].asInt, colorArray[2].asInt) } ?: Colors.WHITE - val customName = jsonElement.get("CustomName")?.asString?.replace("COLOR", "§") ?: "" - val isDev = jsonElement.get("IsDev")?.asBoolean ?: false - randoms[randomsName] = RandomPlayer(size, wings, Color(wingsColor.red, wingsColor.green, wingsColor.blue), customName, isDev) - } + suspend fun updateCustomProperties() { + val response = fetchJson>("https://api.odtheking.com/devs/").getOrNull() ?: return + randoms.putAll(response.associateBy { it.name }) + } + + fun buildDevBody(devName: String, wingsColor: Color, sizeX: Float, sizeY: Float, sizeZ: Float, wings: Boolean, customName: String, password: String): String { + return """ { + "devName": "$devName", + "wingsColor": [${wingsColor.red}, ${wingsColor.green}, ${wingsColor.blue}], + "size": [$sizeX, $sizeY, $sizeZ], + "wings": $wings, + "customName": "$customName", + "password": "$password" + } """.trimIndent() } init { @@ -97,8 +112,7 @@ object PlayerSize : Module( fun replaceText(text: String?): String? { var replacedText = text for ((key, value) in randoms.toMap()) { - if (value.customName.isBlank()) continue - replacedText = value.customName.let { replacedText?.replace(key, it) } + if (value.customName?.isBlank() == false) replacedText = value.customName.let { replacedText?.replace(key, it) } } return replacedText @@ -136,11 +150,11 @@ object PlayerSize : Module( val x = player.lastTickPosX + (player.posX - player.lastTickPosX) * partialTicks val y = player.lastTickPosY + (player.posY - player.lastTickPosY) * partialTicks val z = player.lastTickPosZ + (player.posZ - player.lastTickPosZ) * partialTicks - if (random.scale.second < 0) GlStateManager.translate(0f, random.scale.second * -2, 0f) + if (random.scale[1] < 0) GlStateManager.translate(0f, random.scale[1] * -2, 0f) GlStateManager.translate(-mc.renderManager.viewerPosX + x, -mc.renderManager.viewerPosY + y, -mc.renderManager.viewerPosZ + z) GlStateManager.scale(-0.2, -0.2, 0.2) - GlStateManager.scale(random.scale.first, random.scale.second, random.scale.third) + GlStateManager.scale(random.scale[0], random.scale[1], random.scale[2]) GlStateManager.rotate(180 + rotation, 0f, 1f, 0f) GlStateManager.translate(0.0, -(1.25 / 0.2f), 0.0) GlStateManager.translate(0.0, 0.0, 0.25) @@ -150,7 +164,7 @@ object PlayerSize : Module( GlStateManager.translate(0.0, 1.0, -0.5) } - GlStateManager.color(random.wingsColor.red.toFloat()/255, random.wingsColor.green.toFloat()/255, random.wingsColor.blue.toFloat()/255, 1f) + GlStateManager.color(random.wingsColor[0].toFloat()/255, random.wingsColor[1].toFloat()/255, random.wingsColor[2].toFloat()/255, 1f) mc.textureManager.bindTexture(dragonWingTextureLocation) for (j in 0..1) { diff --git a/src/main/kotlin/me/odinmain/utils/WebUtils.kt b/src/main/kotlin/me/odinmain/utils/WebUtils.kt deleted file mode 100644 index cb900d926..000000000 --- a/src/main/kotlin/me/odinmain/utils/WebUtils.kt +++ /dev/null @@ -1,105 +0,0 @@ -package me.odinmain.utils - -import com.google.gson.JsonParser -import kotlinx.coroutines.withTimeoutOrNull -import me.odinmain.OdinMain.logger -import java.io.* -import java.net.HttpURLConnection -import java.net.URI -import java.net.URL - -fun setupConnection(url: String, timeout: Int = 5000, useCaches: Boolean = true): InputStream { - val connection = URI(url).toURL().openConnection() as HttpURLConnection - connection.setRequestMethod("GET") - connection.setUseCaches(useCaches) - connection.addRequestProperty("User-Agent", "Odin") - connection.setReadTimeout(timeout) - connection.setConnectTimeout(timeout) - connection.setDoOutput(true) - return connection.inputStream -} - -fun fetchData(url: String, timeout: Int = 5000, useCaches: Boolean = true): String = - setupConnection(url, timeout, useCaches).bufferedReader().use { it.readText() } - -fun sendDataToServer(body: String, url: String = "https://gi2wsqbyse6tnfhqakbnq6f2su0vujgz.lambda-url.eu-north-1.on.aws/"): String { - return try { - val connection = URI(url).toURL().openConnection() as HttpURLConnection - connection.setRequestMethod("POST") - connection.setDoOutput(true) - - with (OutputStreamWriter(connection.outputStream)) { - write(body) - flush() - } - - connection.disconnect() - - connection.inputStream.bufferedReader().use { it.readText() } - } catch (_: Exception) { "" } -} - -/** - * Fetches data from a specified URL and returns it as a string. - * - * @param url The URL from which to fetch data. - * @return A string containing the data fetched from the URL, or an empty string in case of an exception. - */ -fun fetchURLData(url: String): String { - try { - // Open a connection to the specified URL - val connection = URL(url).openConnection() - - // Set the user agent to emulate a web browser - connection.setRequestProperty( - "User-Agent", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3" - ) - - // Read the content from the input stream and build a string - val inputStream = connection.getInputStream() - val reader = BufferedReader(InputStreamReader(inputStream)) - val content = StringBuilder() - - var line: String? - while (reader.readLine().also { line = it } != null) { - content.append(line) - } - - // Close the reader and return the content as a string - reader.close() - return content.toString() - } catch (e: Exception) { - // Print the stack trace in case of an exception and return an empty string - logger.error("Error fetching data from URL: $url", e) - return "Failed to fetch content from URL: $url" - } -} - -fun downloadFile(url: String, outputPath: String) { - val url = URL(url) - val connection = url.openConnection() - connection.connect() - - val inputStream: InputStream = connection.getInputStream() - val outputFile = File(outputPath) - - outputFile.parentFile?.mkdirs() - - val outputStream = FileOutputStream(outputFile) - inputStream.use { input -> - outputStream.use { output -> - input.copyTo(output) - } - } -} - -suspend fun hasBonusPaulScore(): Boolean = withTimeoutOrNull(5000) { - val response: String = URL("https://api.hypixel.net/resources/skyblock/election").readText() - val jsonObject = JsonParser().parse(response).asJsonObject - val mayor = jsonObject.getAsJsonObject("mayor") ?: return@withTimeoutOrNull false - val name = mayor.get("name")?.asString ?: return@withTimeoutOrNull false - return@withTimeoutOrNull if (name == "Paul") { - mayor.getAsJsonArray("perks")?.any { it.asJsonObject.get("name")?.asString == "EZPZ" } == true - } else false -} == true diff --git a/src/main/kotlin/me/odinmain/utils/network/SslUtils.kt b/src/main/kotlin/me/odinmain/utils/network/SslUtils.kt new file mode 100644 index 000000000..2a4dedf54 --- /dev/null +++ b/src/main/kotlin/me/odinmain/utils/network/SslUtils.kt @@ -0,0 +1,36 @@ +package me.odinmain.utils.network + +import me.odinmain.OdinMain.logger +import java.security.KeyStore +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManagerFactory +import javax.net.ssl.X509TrustManager + +object SslUtils { + private val TrustManager: TrustManagerFactory by lazy { + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply { + init(KeyStore.getInstance(KeyStore.getDefaultType()).apply { + load(this::class.java.getResourceAsStream("/odincacerts.jks"), "changeit".toCharArray()) + }) + } + } + + fun createSslContext(): SSLContext = SSLContext.getInstance("TLS").apply { + try { + init(null, TrustManager.trustManagers, null) + logger.info("Created SSLContext successfully.") + } catch (e: Exception) { + logger.error("Failed to create SSLContext: ${e.message}") + throw e + } + } + + fun getTrustManager(): X509TrustManager = try { + val manager = TrustManager.trustManagers + if (manager.isNotEmpty()) manager[0] as X509TrustManager + else throw IllegalStateException("No TrustManager found") + } catch(e: Exception) { + logger.error("Failed to get TrustManager: ${e.message}") + throw e + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/odinmain/utils/network/WebSocketConnection.kt b/src/main/kotlin/me/odinmain/utils/network/WebSocketConnection.kt new file mode 100644 index 000000000..d3b622c53 --- /dev/null +++ b/src/main/kotlin/me/odinmain/utils/network/WebSocketConnection.kt @@ -0,0 +1,61 @@ +package me.odinmain.utils.network + +import me.odinmain.OdinMain.okClient +import me.odinmain.OdinMain.logger +import okhttp3.Request +import okhttp3.Response +import okhttp3.WebSocket +import okhttp3.WebSocketListener +import okio.ByteString + +fun webSocket(func: WebSocketConnection.() -> Unit) = WebSocketConnection().apply(func) + +class WebSocketConnection() { + private var _webSocket: WebSocket? = null + private var onMessageFunc: (String) -> Unit = { } + + fun onMessage(func: (String) -> Unit) { + onMessageFunc = func + } + + val connected get() = _webSocket != null + + fun send(message: String) { + _webSocket?.send(message) + } + + fun connect(url: String) { + shutdown() + val request = Request.Builder().url(url).build() + val listener = object : WebSocketListener() { + override fun onOpen(webSocket: WebSocket, response: Response) { + logger.info("WebSocket connected to $url") + _webSocket = webSocket + } + + override fun onMessage(webSocket: WebSocket, text: String) { + onMessageFunc(text) + } + + override fun onMessage(webSocket: WebSocket, bytes: ByteString) { + onMessageFunc(bytes.utf8()) + } + + override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { + webSocket.close(code, reason) + logger.info("WebSocket closing: $code / $reason") + } + + override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { + logger.error("WebSocket error: ${t.message}", t) + } + } + + _webSocket = okClient.newWebSocket(request, listener) + } + + fun shutdown() { + _webSocket?.close(1000, "Client shutdown") + _webSocket = null + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/odinmain/utils/network/WebUtils.kt b/src/main/kotlin/me/odinmain/utils/network/WebUtils.kt new file mode 100644 index 000000000..0684055bd --- /dev/null +++ b/src/main/kotlin/me/odinmain/utils/network/WebUtils.kt @@ -0,0 +1,87 @@ +package me.odinmain.utils.network + +import com.google.gson.Gson +import com.google.gson.JsonObject +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withTimeoutOrNull +import me.odinmain.OdinMain.gson +import me.odinmain.OdinMain.logger +import me.odinmain.OdinMain.okClient +import me.odinmain.utils.network.SslUtils.createSslContext +import me.odinmain.utils.network.SslUtils.getTrustManager +import okhttp3.* +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import org.apache.http.impl.EnglishReasonPhraseCatalog +import java.io.InputStream +import java.util.concurrent.TimeUnit +import kotlin.coroutines.resume + +object WebUtils { + private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3" + private val JSON = "application/json; charset=utf-8".toMediaType() + + suspend inline fun fetchJson(url: String, json: Gson = gson): Result = runCatching { + json.fromJson(fetchString(url).getOrElse { return Result.failure(it) }, T::class.java) + } + + suspend inline fun fetchString(url: String): Result = getInputStream(url).map { it.bufferedReader().use { reader -> reader.readText() } } + + suspend fun getInputStream(url: String): Result = + clientCall(Request.Builder().url(url).build()) + .onFailure { e -> logger.warn("Failed to get input stream. Error: ${e.message}") } + + suspend fun postData(url: String, body: String): Result = + clientCall(Request.Builder().url(url).post(body.toRequestBody(JSON)).build()) + .map { it.bufferedReader().use { reader -> reader.readText() } } + .onFailure { e -> logger.warn("Failed to post data. Error: ${e.message}") } + + private suspend fun clientCall(request: Request): Result = suspendCancellableCoroutine { cont -> + logger.info("Making request to ${request.url}") + + val callback = object : Callback { + override fun onFailure(call: Call, e: java.io.IOException) { + cont.resume(Result.failure(e)) + } + + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful && response.body != null) response.body?.let { cont.resume(Result.success(it.byteStream())) } + else cont.resume(Result.failure(InputStreamException(response.code, request.url.toString()))) + } + } + + okClient.newCall(request).enqueue(callback) + } + + fun createClient(): OkHttpClient = OkHttpClient.Builder().apply { + sslSocketFactory(createSslContext().socketFactory, getTrustManager()) + + dispatcher(Dispatcher().apply { + maxRequests = 10 + maxRequestsPerHost = 5 + }) + + readTimeout(10, TimeUnit.SECONDS) + connectTimeout(5, TimeUnit.SECONDS) + writeTimeout(10, TimeUnit.SECONDS) + + addInterceptor { chain -> + chain.request().newBuilder() + .header("Accept", "application/json") + .header("User-Agent", USER_AGENT) + .build() + .let { chain.proceed(it) } + } + }.build() + + suspend fun hasBonusPaulScore(): Boolean = withTimeoutOrNull(5000) { + val response = fetchJson("https://api.hypixel.net/resources/skyblock/election").getOrElse { return@withTimeoutOrNull false } + val mayor = response.getAsJsonObject("mayor") ?: return@withTimeoutOrNull false + val name = mayor.get("name")?.asString ?: return@withTimeoutOrNull false + return@withTimeoutOrNull if (name == "Paul") { + mayor.getAsJsonArray("perks")?.any { it.asJsonObject.get("name")?.asString == "EZPZ" } == true + } else false + } == true + + class InputStreamException(code: Int, url: String) : Exception("Failed to get input stream from $url: ${EnglishReasonPhraseCatalog.INSTANCE.getReason(code, null)}") +} \ No newline at end of file diff --git a/src/main/kotlin/me/odinmain/utils/skyblock/LocationUtils.kt b/src/main/kotlin/me/odinmain/utils/skyblock/LocationUtils.kt index 4068dc9fe..fda6a70b6 100644 --- a/src/main/kotlin/me/odinmain/utils/skyblock/LocationUtils.kt +++ b/src/main/kotlin/me/odinmain/utils/skyblock/LocationUtils.kt @@ -3,9 +3,11 @@ package me.odinmain.utils.skyblock import me.odinmain.OdinMain.mc import me.odinmain.events.impl.PacketEvent import me.odinmain.utils.equalsOneOf +import me.odinmain.utils.noControlCodes import me.odinmain.utils.startsWithOneOf import net.minecraft.network.play.server.S38PacketPlayerListItem import net.minecraft.network.play.server.S3BPacketScoreboardObjective +import net.minecraft.network.play.server.S3EPacketTeams import net.minecraft.network.play.server.S3FPacketCustomPayload import net.minecraftforge.event.world.WorldEvent import net.minecraftforge.fml.common.eventhandler.EventPriority @@ -20,6 +22,11 @@ object LocationUtils { var currentArea: Island = Island.Unknown private set + var lobbyId: String? = null + private set + + val lobbyRegex = Regex("\\d\\d/\\d\\d/\\d\\d (\\w{0,6}) *") + @SubscribeEvent fun onDisconnect(event: FMLNetworkEvent.ClientDisconnectionFromServerEvent) { currentArea = Island.Unknown @@ -65,6 +72,13 @@ object LocationUtils { is S3BPacketScoreboardObjective -> if (!isInSkyblock) isInSkyblock = isOnHypixel && event.packet.func_149339_c() == "SBScoreboard" + + is S3EPacketTeams -> { + if (!currentArea.isArea(Island.Unknown) || event.packet.action != 2) return + val text = event.packet.prefix?.plus(event.packet.suffix)?.noControlCodes ?: return + + lobbyRegex.find(text)?.groupValues?.get(1)?.let { lobbyId = it } ?: return + } } } } \ No newline at end of file diff --git a/src/main/kotlin/me/odinmain/utils/skyblock/dungeon/DungeonListener.kt b/src/main/kotlin/me/odinmain/utils/skyblock/dungeon/DungeonListener.kt index 15489d1a9..904542c1c 100644 --- a/src/main/kotlin/me/odinmain/utils/skyblock/dungeon/DungeonListener.kt +++ b/src/main/kotlin/me/odinmain/utils/skyblock/dungeon/DungeonListener.kt @@ -11,7 +11,7 @@ import me.odinmain.features.impl.dungeon.LeapMenu.odinSorting import me.odinmain.features.impl.dungeon.MapInfo.shownTitle import me.odinmain.features.impl.dungeon.Mimic import me.odinmain.utils.equalsOneOf -import me.odinmain.utils.hasBonusPaulScore +import me.odinmain.utils.network.WebUtils.hasBonusPaulScore import me.odinmain.utils.noControlCodes import me.odinmain.utils.romanToInt import me.odinmain.utils.skyblock.PlayerUtils.posX diff --git a/src/main/kotlin/me/odinmain/utils/ui/rendering/Image.kt b/src/main/kotlin/me/odinmain/utils/ui/rendering/Image.kt index 2c93d88ea..dcc852cb2 100644 --- a/src/main/kotlin/me/odinmain/utils/ui/rendering/Image.kt +++ b/src/main/kotlin/me/odinmain/utils/ui/rendering/Image.kt @@ -1,6 +1,7 @@ package me.odinmain.utils.ui.rendering -import me.odinmain.utils.setupConnection +import kotlinx.coroutines.runBlocking +import me.odinmain.utils.network.WebUtils.getInputStream import java.io.File import java.io.FileNotFoundException import java.io.InputStream @@ -10,17 +11,12 @@ import java.nio.file.Files class Image( val identifier: String, - var isSVG: Boolean = false, + val isSVG: Boolean = identifier.endsWith(".svg", true), var stream: InputStream = getStream(identifier), private var buffer: ByteBuffer? = null ) { - constructor(inputStream: InputStream, identifier: String) : this(identifier, false, inputStream) { stream = inputStream } - init { - isSVG = identifier.endsWith(".svg", true) - } - fun buffer(): ByteBuffer { if (buffer == null) { val bytes = stream.readBytes() @@ -44,7 +40,7 @@ class Image( private fun getStream(path: String): InputStream { val trimmedPath = path.trim() - return if (trimmedPath.startsWith("http")) setupConnection(trimmedPath) + return if (trimmedPath.startsWith("http")) runBlocking { getInputStream(trimmedPath).getOrThrow() } // this is really weird but the old one behaved the same... else { val file = File(trimmedPath) if (file.exists() && file.isFile) Files.newInputStream(file.toPath()) diff --git a/src/main/resources/odincacerts.jks b/src/main/resources/odincacerts.jks new file mode 100644 index 000000000..a4bf07233 Binary files /dev/null and b/src/main/resources/odincacerts.jks differ