diff options
Diffstat (limited to 'src/main/java/at/hannibal2/skyhanni/utils')
60 files changed, 605 insertions, 513 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/APIUtil.kt b/src/main/java/at/hannibal2/skyhanni/utils/APIUtil.kt index 2fd634d26..0f561fdff 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/APIUtil.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/APIUtil.kt @@ -22,8 +22,8 @@ import java.io.FileInputStream import java.io.InputStreamReader import java.nio.charset.StandardCharsets - object APIUtil { + private val parser = JsonParser() private var showApiErrors = false @@ -49,7 +49,7 @@ object APIUtil { fun getJSONResponseAsElement( urlString: String, silentError: Boolean = false, - apiName: String = "Hypixel API" + apiName: String = "Hypixel API", ): JsonElement { val client = builder.build() try { @@ -63,16 +63,15 @@ object APIUtil { if (e.message?.contains("Use JsonReader.setLenient(true)") == true) { println("MalformedJsonException: Use JsonReader.setLenient(true)") println(" - getJSONResponse: '$urlString'") - LorenzUtils.debug("MalformedJsonException: Use JsonReader.setLenient(true)") + ChatUtils.debug("MalformedJsonException: Use JsonReader.setLenient(true)") } else if (retSrc.contains("<center><h1>502 Bad Gateway</h1></center>")) { if (showApiErrors && apiName == "Hypixel API") { - LorenzUtils.clickableChat( + ChatUtils.clickableChat( "Problems with detecting the Hypixel API. §eClick here to hide this message for now.", "shtogglehypixelapierrors" ) } e.printStackTrace() - } else { ErrorManager.logErrorWithData( e, "$apiName error for url: '$urlString'", @@ -118,7 +117,7 @@ object APIUtil { val message = "POST request to '$urlString' returned status ${status.statusCode}" println(message) - LorenzUtils.error("SkyHanni ran into an error. Status: ${status.statusCode}") + ChatUtils.error("SkyHanni ran into an error. Status: ${status.statusCode}") return ApiResponse(false, message, JsonObject()) } } catch (throwable: Throwable) { @@ -126,7 +125,7 @@ object APIUtil { throw throwable } else { throwable.printStackTrace() - LorenzUtils.error("SkyHanni ran into an ${throwable::class.simpleName ?: "error"} whilst sending a resource. See logs for more details.") + ChatUtils.error("SkyHanni ran into an ${throwable::class.simpleName ?: "error"} whilst sending a resource. See logs for more details.") } return ApiResponse(false, throwable.message, JsonObject()) } finally { @@ -139,15 +138,21 @@ object APIUtil { return parser.parse(retSrc) as JsonObject } - fun postJSONIsSuccessful(urlString: String, body: String, silentError: Boolean = false): Boolean { - val response = postJSON(urlString, body, silentError) + fun postJSONIsSuccessful(url: String, body: String, silentError: Boolean = false): Boolean { + val response = postJSON(url, body, silentError) if (response.success) { return true } println(response.message) - LorenzUtils.error(response.message ?: "An error occurred during the API request") + ErrorManager.logErrorStateWithData( + "An error occurred during the API request", + "unsuccessful API response", + "url" to url, + "body" to body, + "response" to response, + ) return false } @@ -158,6 +163,6 @@ object APIUtil { fun toggleApiErrorMessages() { showApiErrors = !showApiErrors - LorenzUtils.chat("Hypixel API error messages " + if (showApiErrors) "§chidden" else "§ashown") + ChatUtils.chat("Hypixel API error messages " + if (showApiErrors) "§chidden" else "§ashown") } } diff --git a/src/main/java/at/hannibal2/skyhanni/utils/ChatUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/ChatUtils.kt new file mode 100644 index 000000000..34f42e629 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/ChatUtils.kt @@ -0,0 +1,203 @@ +package at.hannibal2.skyhanni.utils + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.events.LorenzTickEvent +import at.hannibal2.skyhanni.utils.StringUtils.removeColor +import net.minecraft.client.Minecraft +import net.minecraft.event.ClickEvent +import net.minecraft.event.HoverEvent +import net.minecraft.util.ChatComponentText +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import java.util.LinkedList +import java.util.Queue +import kotlin.time.Duration.Companion.milliseconds + +object ChatUtils { + + // TODO log based on chat category (error, warning, debug, user error, normal) + private val log = LorenzLogger("chat/mod_sent") + var lastButtonClicked = 0L + + private const val DEBUG_PREFIX = "[SkyHanni Debug] §7" + private const val USER_ERROR_PREFIX = "§c[SkyHanni] " + private val ERROR_PREFIX by lazy { "§c[SkyHanni-${SkyHanniMod.version}] " } + private const val CHAT_PREFIX = "[SkyHanni] " + + /** + * Sends a debug message to the chat and the console. + * This is only sent if the debug feature is enabled. + * + * @param message The message to be sent + * + * @see DEBUG_PREFIX + */ + fun debug(message: String) { + if (SkyHanniMod.feature.dev.debug.enabled && internalChat(DEBUG_PREFIX + message)) { + LorenzUtils.consoleLog("[Debug] $message") + } + } + + /** + * Sends a message to the user that they did something incorrectly. + * We should tell them what to do instead as well. + * + * @param message The message to be sent + * + * @see USER_ERROR_PREFIX + */ + fun userError(message: String) { + internalChat(USER_ERROR_PREFIX + message) + } + + /** + * Sends a message to the user that an error occurred caused by something in the code. + * This should be used for errors that are not caused by the user. + * + * Why deprecate this? Even if this message is descriptive for the user and the developer, + * we don't want inconsitencies in errors, and we would need to search + * for the code line where this error gets printed any way. + * so it's better to use the stack trace still. + * + * @param message The message to be sent + * @param prefix Whether to prefix the message with the error prefix, default true + * + * @see ERROR_PREFIX + */ + @Deprecated( + "Do not send the user a non clickable non stacktrace containing error message.", + ReplaceWith("ErrorManager.logErrorStateWithData") + ) + fun error(message: String) { + println("error: '$message'") + internalChat(ERROR_PREFIX + message) + } + + /** + * Sends a message to the user + * @param message The message to be sent + * @param prefix Whether to prefix the message with the chat prefix, default true + * @param prefixColor Color that the prefix should be, default yellow (§e) + * + * @see CHAT_PREFIX + */ + fun chat(message: String, prefix: Boolean = true, prefixColor: String = "§e") { + if (prefix) { + internalChat(prefixColor + CHAT_PREFIX + message) + } else { + internalChat(message) + } + } + + private fun internalChat(message: String): Boolean { + log.log(message) + val minecraft = Minecraft.getMinecraft() + if (minecraft == null) { + LorenzUtils.consoleLog(message.removeColor()) + return false + } + + val thePlayer = minecraft.thePlayer + if (thePlayer == null) { + LorenzUtils.consoleLog(message.removeColor()) + return false + } + + thePlayer.addChatMessage(ChatComponentText(message)) + return true + } + + /** + * Sends a message to the user that they can click and run a command + * @param message The message to be sent + * @param command The command to be executed when the message is clicked + * @param prefix Whether to prefix the message with the chat prefix, default true + * @param prefixColor Color that the prefix should be, default yellow (§e) + * + * @see CHAT_PREFIX + */ + fun clickableChat(message: String, command: String, prefix: Boolean = true, prefixColor: String = "§e") { + val msgPrefix = if (prefix) prefixColor + CHAT_PREFIX else "" + val text = ChatComponentText(msgPrefix + message) + val fullCommand = "/" + command.removePrefix("/") + text.chatStyle.chatClickEvent = ClickEvent(ClickEvent.Action.RUN_COMMAND, fullCommand) + text.chatStyle.chatHoverEvent = + HoverEvent(HoverEvent.Action.SHOW_TEXT, ChatComponentText("§eExecute $fullCommand")) + Minecraft.getMinecraft().thePlayer.addChatMessage(text) + } + + /** + * Sends a message to the user that they can click and run a command + * @param message The message to be sent + * @param hover The message to be shown when the message is hovered + * @param command The command to be executed when the message is clicked + * @param prefix Whether to prefix the message with the chat prefix, default true + * @param prefixColor Color that the prefix should be, default yellow (§e) + * + * @see CHAT_PREFIX + */ + fun hoverableChat( + message: String, + hover: List<String>, + command: String? = null, + prefix: Boolean = true, + prefixColor: String = "§e", + ) { + val msgPrefix = if (prefix) prefixColor + CHAT_PREFIX else "" + val text = ChatComponentText(msgPrefix + message) + text.chatStyle.chatHoverEvent = + HoverEvent(HoverEvent.Action.SHOW_TEXT, ChatComponentText(hover.joinToString("\n"))) + + command?.let { + text.chatStyle.chatClickEvent = ClickEvent(ClickEvent.Action.RUN_COMMAND, "/${it.removePrefix("/")}") + } + + Minecraft.getMinecraft().thePlayer.addChatMessage(text) + } + + /** + * Sends a message to the user that they can click and run a command + * @param message The message to be sent + * @param url The url to be opened + * @param autoOpen Automatically opens the url as well as sending the clickable link message + * @param hover The message to be shown when the message is hovered + * @param prefix Whether to prefix the message with the chat prefix, default true + * @param prefixColor Color that the prefix should be, default yellow (§e) + * + * @see CHAT_PREFIX + */ + fun clickableLinkChat( + message: String, + url: String, + hover: String = "§eOpen $url", + autoOpen: Boolean = false, + prefix: Boolean = true, + prefixColor: String = "§e" + ) { + val msgPrefix = if (prefix) prefixColor + CHAT_PREFIX else "" + val text = ChatComponentText(msgPrefix + message) + text.chatStyle.chatClickEvent = ClickEvent(ClickEvent.Action.OPEN_URL, url) + text.chatStyle.chatHoverEvent = HoverEvent(HoverEvent.Action.SHOW_TEXT, ChatComponentText("$prefixColor$hover")) + Minecraft.getMinecraft().thePlayer.addChatMessage(text) + if (autoOpen) OSUtils.openBrowser(url) + } + + private var lastMessageSent = SimpleTimeMark.farPast() + private val sendQueue: Queue<String> = LinkedList() + + @SubscribeEvent + fun sendQueuedChatMessages(event: LorenzTickEvent) { + val player = Minecraft.getMinecraft().thePlayer + if (player == null) { + sendQueue.clear() + return + } + if (lastMessageSent.passedSince() > 300.milliseconds) { + player.sendChatMessage(sendQueue.poll() ?: return) + lastMessageSent = SimpleTimeMark.now() + } + } + + fun sendMessageToServer(message: String) { + sendQueue.add(message) + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/ClipboardUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/ClipboardUtils.kt index 31008d9b4..b1d8eb68b 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/ClipboardUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/ClipboardUtils.kt @@ -14,6 +14,7 @@ import java.awt.datatransfer.UnsupportedFlavorException import kotlin.time.Duration.Companion.milliseconds object ClipboardUtils { + private var dispatcher = Dispatchers.IO private var lastClipboardAccessTime = SimpleTimeMark.farPast() @@ -71,4 +72,4 @@ object ClipboardUtils { } } } -}
\ No newline at end of file +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/CollectionUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/CollectionUtils.kt new file mode 100644 index 000000000..b93f17fa4 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/CollectionUtils.kt @@ -0,0 +1,115 @@ +package at.hannibal2.skyhanni.utils + +import java.util.Collections +import java.util.WeakHashMap +import java.util.concurrent.ConcurrentLinkedQueue + +object CollectionUtils { + + fun <E> ConcurrentLinkedQueue<E>.drainTo(list: MutableCollection<E>) { + while (true) + list.add(this.poll() ?: break) + } + + // Let garbage collector handle the removal of entries in this list + fun <T> weakReferenceList(): MutableSet<T> = Collections.newSetFromMap(WeakHashMap<T, Boolean>()) + + fun <T> MutableCollection<T>.filterToMutable(predicate: (T) -> Boolean) = filterTo(mutableListOf(), predicate) + + fun <T> List<T>.indexOfFirst(vararg args: T) = args.map { indexOf(it) }.firstOrNull { it != -1 } + + infix fun <K, V> MutableMap<K, V>.put(pairs: Pair<K, V>) { + this[pairs.first] = pairs.second + } + + // Taken and modified from Skytils + @JvmStatic + fun <T> T.equalsOneOf(vararg other: T): Boolean { + for (obj in other) { + if (this == obj) return true + } + return false + } + + fun <E> List<E>.getOrNull(index: Int): E? { + return if (index in indices) { + get(index) + } else null + } + + fun <T : Any> T?.toSingletonListOrEmpty(): List<T> { + if (this == null) return emptyList() + return listOf(this) + } + + fun <K> MutableMap<K, Int>.addOrPut(key: K, number: Int): Int = + this.merge(key, number, Int::plus)!! // Never returns null since "plus" can't return null + + fun <K> MutableMap<K, Long>.addOrPut(key: K, number: Long): Long = + this.merge(key, number, Long::plus)!! // Never returns null since "plus" can't return null + + fun <K> MutableMap<K, Double>.addOrPut(key: K, number: Double): Double = + this.merge(key, number, Double::plus)!! // Never returns null since "plus" can't return null + + fun <K, N : Number> Map<K, N>.sumAllValues(): Double { + if (values.isEmpty()) return 0.0 + + return when (values.first()) { + is Double -> values.sumOf { it.toDouble() } + is Float -> values.sumOf { it.toDouble() } + is Long -> values.sumOf { it.toLong() }.toDouble() + else -> values.sumOf { it.toInt() }.toDouble() + } + } + + fun List<String>.nextAfter(after: String, skip: Int = 1) = nextAfter({ it == after }, skip) + + fun List<String>.nextAfter(after: (String) -> Boolean, skip: Int = 1): String? { + var missing = -1 + for (line in this) { + if (after(line)) { + missing = skip - 1 + continue + } + if (missing == 0) { + return line + } + if (missing != -1) { + missing-- + } + } + return null + } + + fun <K, V> Map<K, V>.editCopy(function: MutableMap<K, V>.() -> Unit) = + toMutableMap().also { function(it) }.toMap() + + fun <T> List<T>.editCopy(function: MutableList<T>.() -> Unit) = + toMutableList().also { function(it) }.toList() + + fun <K, V> Map<K, V>.moveEntryToTop(matcher: (Map.Entry<K, V>) -> Boolean): Map<K, V> { + val entry = entries.find(matcher) + if (entry != null) { + val newMap = linkedMapOf(entry.key to entry.value) + newMap.putAll(this) + return newMap + } + return this + } + + fun <E> MutableList<List<E>>.addAsSingletonList(text: E) { + add(Collections.singletonList(text)) + } + + fun <K, V : Comparable<V>> List<Pair<K, V>>.sorted(): List<Pair<K, V>> { + return sortedBy { (_, value) -> value } + } + + fun <K, V : Comparable<V>> Map<K, V>.sorted(): Map<K, V> { + return toList().sorted().toMap() + } + + fun <K, V : Comparable<V>> Map<K, V>.sortedDesc(): Map<K, V> { + return toList().sorted().reversed().toMap() + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/ColorUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/ColorUtils.kt index 076afc0c4..8eab43636 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/ColorUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/ColorUtils.kt @@ -1,6 +1,11 @@ package at.hannibal2.skyhanni.utils +import java.awt.Color + object ColorUtils { + + /** transfer string colors from the config to java.awt.Color */ + fun String.toChromaColor() = Color(SpecialColour.specialToChromaRGB(this), true) fun getRed(colour: Int) = colour shr 16 and 0xFF fun getGreen(colour: Int) = colour shr 8 and 0xFF @@ -8,4 +13,4 @@ object ColorUtils { fun getBlue(colour: Int) = colour and 0xFF fun getAlpha(colour: Int) = colour shr 24 and 0xFF -}
\ No newline at end o |
