summaryrefslogtreecommitdiff
path: root/src/main/java/at/hannibal2/skyhanni/utils
diff options
context:
space:
mode:
authorThunderblade73 <85900443+Thunderblade73@users.noreply.github.com>2024-06-10 03:11:42 +0200
committerGitHub <noreply@github.com>2024-06-10 03:11:42 +0200
commit1fe45f026afeff9d87107380bf8c4cf58051b29f (patch)
tree362a2d3cf9f0e1c4dbfabb710cd51ab0c7c11ee3 /src/main/java/at/hannibal2/skyhanni/utils
parentb1ff611e4f764c5fba338571540f0ad2389994ac (diff)
downloadskyhanni-1fe45f026afeff9d87107380bf8c4cf58051b29f.tar.gz
skyhanni-1fe45f026afeff9d87107380bf8c4cf58051b29f.tar.bz2
skyhanni-1fe45f026afeff9d87107380bf8c4cf58051b29f.zip
Backend: Graph Editor (#1454)
Co-authored-by: Cal <cwolfson58@gmail.com> Co-authored-by: CalMWolfs <94038482+CalMWolfs@users.noreply.github.com> Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com>
Diffstat (limited to 'src/main/java/at/hannibal2/skyhanni/utils')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/ConditionalUtils.kt26
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/KeyboardManager.kt39
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt66
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt94
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt30
5 files changed, 183 insertions, 72 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/ConditionalUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/ConditionalUtils.kt
index 00aec1dc5..05e53b802 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/ConditionalUtils.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/ConditionalUtils.kt
@@ -30,4 +30,30 @@ object ConditionalUtils {
whenChanged { _, new -> observer(new) }
}
+ fun <T : Comparable<T>, K> comparatorFirst(pair1: Pair<T?, K>, pair2: Pair<T?, K>): Int {
+ val first1 = pair1.first
+ val first2 = pair2.first
+
+ // Handle null cases
+ if (first1 == null && first2 == null) return 0
+ if (first1 == null) return -1
+ if (first2 == null) return 1
+
+ // Compare the non-null first values
+ return first1.compareTo(first2)
+ }
+
+ fun <T, K : Comparable<K>> comparatorSecond(pair1: Pair<T, K?>, pair2: Pair<T, K?>): Int {
+ val second1 = pair1.second
+ val second2 = pair2.second
+
+ // Handle null cases
+ if (second1 == null && second2 == null) return 0
+ if (second1 == null) return 1
+ if (second2 == null) return -1
+
+ // Compare the non-null second values
+ return second1.compareTo(second2)
+ }
+
}
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/KeyboardManager.kt b/src/main/java/at/hannibal2/skyhanni/utils/KeyboardManager.kt
index c04057e72..9bf129be2 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/KeyboardManager.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/KeyboardManager.kt
@@ -133,4 +133,43 @@ object KeyboardManager {
}
fun getKeyName(keyCode: Int): String = KeybindHelper.getKeyName(keyCode)
+
+ object WasdInputMatrix : Iterable<KeyBinding> {
+ operator fun contains(keyBinding: KeyBinding) = when (keyBinding) {
+ w, a, s, d, up, down -> true
+ else -> false
+ }
+
+ val w get() = Minecraft.getMinecraft().gameSettings.keyBindForward!!
+ val a get() = Minecraft.getMinecraft().gameSettings.keyBindLeft!!
+ val s get() = Minecraft.getMinecraft().gameSettings.keyBindBack!!
+ val d get() = Minecraft.getMinecraft().gameSettings.keyBindRight!!
+
+ val up get() = Minecraft.getMinecraft().gameSettings.keyBindJump!!
+ val down get() = Minecraft.getMinecraft().gameSettings.keyBindSneak!!
+
+ override fun iterator(): Iterator<KeyBinding> =
+ object : Iterator<KeyBinding> {
+
+ var current = w
+
+ override fun hasNext(): Boolean =
+ current != down
+
+ override fun next(): KeyBinding {
+ return current.also {
+ current = when (it) {
+ w -> a
+ a -> s
+ s -> d
+ d -> up
+ up -> down
+ else -> throw java.lang.IndexOutOfBoundsException()
+ }
+ }
+ }
+
+ }
+
+ }
}
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt
index f52f7f72b..698098306 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/LorenzUtils.kt
@@ -15,10 +15,12 @@ import at.hannibal2.skyhanni.test.TestBingo
import at.hannibal2.skyhanni.utils.ChatUtils.lastButtonClicked
import at.hannibal2.skyhanni.utils.ItemUtils.getItemCategoryOrNull
import at.hannibal2.skyhanni.utils.NEUItems.getItemStackOrNull
+import at.hannibal2.skyhanni.utils.SimpleTimeMark.Companion.fromNow
import at.hannibal2.skyhanni.utils.StringUtils.capAtMinecraftLength
import at.hannibal2.skyhanni.utils.StringUtils.removeColor
import at.hannibal2.skyhanni.utils.StringUtils.stripHypixelMessage
import at.hannibal2.skyhanni.utils.StringUtils.toDashlessUUID
+import at.hannibal2.skyhanni.utils.TimeUtils.ticks
import at.hannibal2.skyhanni.utils.renderables.Renderable
import com.google.gson.JsonPrimitive
import net.minecraft.client.Minecraft
@@ -178,7 +180,7 @@ object LorenzUtils {
val left = Renderable.hoverTips(
entry.left,
tips = entry.hover,
- highlightsOnHoverSlots = entry.highlightsOnHoverSlots
+ highlightsOnHoverSlots = entry.highlightsOnHoverSlots,
)
val right = Renderable.string(entry.right)
outerList.add(listOf(item, left, right))
@@ -239,9 +241,11 @@ object LorenzUtils {
add("§a[$display]")
} else {
add("§e[")
- add(Renderable.link("§e$display") {
- onChange(entry)
- })
+ add(
+ Renderable.link("§e$display") {
+ onChange(entry)
+ },
+ )
add("§e]")
}
add(" ")
@@ -262,25 +266,29 @@ object LorenzUtils {
lastButtonClicked = System.currentTimeMillis()
}
}
- add(buildList {
- add(prefix)
- add("§a[")
- if (tips.isEmpty()) {
- add(Renderable.link("§e$getName", false, onClick))
- } else {
- add(Renderable.clickAndHover("§e$getName", tips, false, onClick))
- }
- add("§a]")
- })
+ add(
+ buildList {
+ add(prefix)
+ add("§a[")
+ if (tips.isEmpty()) {
+ add(Renderable.link("§e$getName", false, onClick))
+ } else {
+ add(Renderable.clickAndHover("§e$getName", tips, false, onClick))
+ }
+ add("§a]")
+ },
+ )
}
fun GuiEditSign.isRancherSign(): Boolean {
if (this !is AccessorGuiEditSign) return false
val tileSign = (this as AccessorGuiEditSign).tileSign
- return (tileSign.signText[1].unformattedText.removeColor() == "^^^^^^"
- && tileSign.signText[2].unformattedText.removeColor() == "Set your"
- && tileSign.signText[3].unformattedText.removeColor() == "speed cap!")
+ return (
+ tileSign.signText[1].unformattedText.removeColor() == "^^^^^^" &&
+ tileSign.signText[2].unformattedText.removeColor() == "Set your" &&
+ tileSign.signText[3].unformattedText.removeColor() == "speed cap!"
+ )
}
fun IslandType.isInIsland() = inSkyBlock && skyBlockIsland == this
@@ -291,7 +299,11 @@ object LorenzUtils {
if (this.clickedButton == 1 && slot?.stack?.getItemCategoryOrNull() == ItemCategory.SACK) return
slot?.slotNumber?.let { slotNumber ->
Minecraft.getMinecraft().playerController.windowClick(
- container.windowId, slotNumber, 0, 1, Minecraft.getMinecraft().thePlayer
+ container.windowId,
+ slotNumber,
+ 0,
+ 1,
+ Minecraft.getMinecraft().thePlayer,
)
this.cancel()
}
@@ -344,14 +356,24 @@ object LorenzUtils {
@Deprecated("Use the new one instead", ReplaceWith("RegexUtils.hasGroup"))
fun Matcher.hasGroup(groupName: String): Boolean = groupOrNull(groupName) != null
- fun inAdvancedMiningIsland() =
- IslandType.DWARVEN_MINES.isInIsland() || IslandType.CRYSTAL_HOLLOWS.isInIsland() || IslandType.MINESHAFT.isInIsland()
+ fun inAdvancedMiningIsland() = IslandType.DWARVEN_MINES.isInIsland() ||
+ IslandType.CRYSTAL_HOLLOWS.isInIsland() || IslandType.MINESHAFT.isInIsland()
- fun inMiningIsland() = IslandType.GOLD_MINES.isInIsland() || IslandType.DEEP_CAVERNS.isInIsland()
- || inAdvancedMiningIsland()
+ fun inMiningIsland() = IslandType.GOLD_MINES.isInIsland() ||
+ IslandType.DEEP_CAVERNS.isInIsland() || inAdvancedMiningIsland()
fun isBetaVersion() = UpdateManager.isCurrentlyBeta()
+ private var lastGuiTime = SimpleTimeMark.farPast()
+
+ fun isAnyGuiActive(): Boolean {
+ val gui = Minecraft.getMinecraft().currentScreen != null
+ if (gui) {
+ lastGuiTime = 3.ticks.fromNow()
+ }
+ return !lastGuiTime.isInPast()
+ }
+
fun AxisAlignedBB.getCorners(y: Double): List<LorenzVec> {
val cornerOne = LorenzVec(minX, y, minZ)
val cornerTwo = LorenzVec(minX, y, maxZ)
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt
index 1506882cd..e3c4b6d7b 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt
@@ -74,7 +74,7 @@ object RenderUtils {
private val beaconBeam = ResourceLocation("textures/entity/beacon_beam.png")
- private val matrixBuffer: FloatBuffer = GLAllocation.createDirectFloatBuffer(16);
+ private val matrixBuffer: FloatBuffer = GLAllocation.createDirectFloatBuffer(16)
private val colourBuffer: FloatBuffer = GLAllocation.createDirectFloatBuffer(16)
private val bezier2Buffer: FloatBuffer = GLAllocation.createDirectFloatBuffer(9)
@@ -166,7 +166,7 @@ object RenderUtils {
drawFilledBoundingBox(
AxisAlignedBB(x, y, z, x + 1, y + 1, z + 1).expandBlock(),
color,
- realAlpha
+ realAlpha,
)
GlStateManager.disableTexture2D()
if (distSq > 5 * 5 && beacon) renderBeaconBeam(x, y + 1, z, color.rgb, 1.0f, partialTicks)
@@ -222,7 +222,7 @@ object RenderUtils {
val time = Minecraft.getMinecraft().theWorld.totalWorldTime + partialTicks.toDouble()
val d1 = MathHelper.func_181162_h(
-time * 0.2 - MathHelper.floor_double(-time * 0.1)
- .toDouble()
+ .toDouble(),
)
val r = (rgb shr 16 and 0xFF) / 255f
val g = (rgb shr 8 and 0xFF) / 255f
@@ -352,7 +352,7 @@ object RenderUtils {
Minecraft.getMinecraft().renderManager.playerViewX,
1.0f,
0.0f,
- 0.0f
+ 0.0f,
)
GlStateManager.scale(-f1, -f1, f1)
GlStateManager.disableLighting()
@@ -440,7 +440,7 @@ object RenderUtils {
(-width).toFloat(),
yOff,
LorenzColor.WHITE.toColor().rgb,
- shadow
+ shadow,
)
GlStateManager.disableBlend()
GlStateManager.popMatrix()
@@ -618,8 +618,9 @@ object RenderUtils {
}
} catch (e: NullPointerException) {
ErrorManager.logErrorWithData(
- e, "Failed to render an element",
- "list" to list
+ e,
+ "Failed to render an element",
+ "list" to list,
)
}
GuiEditManager.add(this, posLabel, longestX, offsetY)
@@ -638,8 +639,11 @@ object RenderUtils {
renderRenderables(
listOf(
Renderable.horizontalContainer(
- list.mapNotNull { Renderable.fromAny(it) }
- )), posLabel = posLabel)
+ list.mapNotNull { Renderable.fromAny(it) },
+ ),
+ ),
+ posLabel = posLabel,
+ )
// TODO Future write that better
}
@@ -675,8 +679,9 @@ object RenderUtils {
add(Renderable.itemStack(item, scale))
} catch (e: NullPointerException) {
ErrorManager.logErrorWithData(
- e, "Add item icon to renderable list",
- "item" to item
+ e,
+ "Add item icon to renderable list",
+ "item" to item,
)
}
}
@@ -954,6 +959,7 @@ object RenderUtils {
hideTooCloseAt: Double = 4.5,
smallestDistanceVew: Double = 5.0,
ignoreBlocks: Boolean = true,
+ ignoreY: Boolean = false,
) {
val thePlayer = Minecraft.getMinecraft().thePlayer
val x = location.x
@@ -966,24 +972,29 @@ object RenderUtils {
val renderOffsetZ = render.lastTickPosZ + (render.posZ - render.lastTickPosZ) * partialTicks
val eyeHeight = thePlayer.eyeHeight
- val distToPlayerSq =
- (x - renderOffsetX) * (x - renderOffsetX) + (y - (renderOffsetY + eyeHeight)) * (y - (renderOffsetY + eyeHeight)) + (z - renderOffsetZ) * (z - renderOffsetZ)
+ val dX = (x - renderOffsetX) * (x - renderOffsetX)
+ val dY = (y - (renderOffsetY + eyeHeight)) * (y - (renderOffsetY + eyeHeight))
+ val dZ = (z - renderOffsetZ) * (z - renderOffsetZ)
+ val distToPlayerSq = dX + dY + dZ
var distToPlayer = sqrt(distToPlayerSq)
// TODO this is optional maybe?
distToPlayer = distToPlayer.coerceAtLeast(smallestDistanceVew)
if (distToPlayer < hideTooCloseAt) return
+ if (ignoreBlocks && distToPlayer > 80) return
val distRender = distToPlayer.coerceAtMost(50.0)
+ var scale = distRender / 12
+ scale *= scaleMultiplier
+
val resultX = renderOffsetX + (x + 0.5 - renderOffsetX) / (distToPlayer / distRender)
- val resultY =
- (renderOffsetY + eyeHeight) + (y + 20 * distToPlayer / 300 - (renderOffsetY + eyeHeight)) / (distToPlayer / distRender)
+ val resultY = if (ignoreY) y * distToPlayer / distRender else renderOffsetY + eyeHeight +
+ (y + 20 * distToPlayer / 300 - (renderOffsetY + eyeHeight)) / (distToPlayer / distRender)
val resultZ = renderOffsetZ + (z + 0.5 - renderOffsetZ) / (distToPlayer / distRender)
val renderLocation = LorenzVec(resultX, resultY, resultZ)
- var scale = distRender / 12
- scale *= scaleMultiplier
+
render(renderLocation, "§f$text", scale, !ignoreBlocks, true, yOff)
}
@@ -1010,7 +1021,7 @@ object RenderUtils {
GlStateManager.translate(
location.x - renderManager.viewerPosX,
location.y - renderManager.viewerPosY,
- location.z - renderManager.viewerPosZ
+ location.z - renderManager.viewerPosZ,
)
GlStateManager.color(1f, 1f, 1f, 0.5f)
GlStateManager.rotate(-renderManager.playerViewY, 0.0f, 1.0f, 0.0f)
@@ -1022,14 +1033,14 @@ object RenderUtils {
text,
(-stringWidth / 2).toFloat(),
yOff,
- 0
+ 0,
)
} else {
fontRenderer.drawString(
text,
-stringWidth / 2,
0,
- 0
+ 0,
)
}
GlStateManager.color(1f, 1f, 1f)
@@ -1088,7 +1099,7 @@ object RenderUtils {
c.red / 255f * 0.8f,
c.green / 255f * 0.8f,
c.blue / 255f * 0.8f,
- c.alpha / 255f * alphaMultiplier
+ c.alpha / 255f * alphaMultiplier,
)
// x
@@ -1108,7 +1119,7 @@ object RenderUtils {
c.red / 255f * 0.9f,
c.green / 255f * 0.9f,
c.blue / 255f * 0.9f,
- c.alpha / 255f * alphaMultiplier
+ c.alpha / 255f * alphaMultiplier,
)
// z
worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION)
@@ -1142,6 +1153,7 @@ object RenderUtils {
drawFilledBoundingBox_nea(aabb, c, alphaMultiplier, renderRelativeToCamera, drawVerticalBarriers, partialTicks)
}
+ @Suppress("ktlint:standard:function-naming")
fun drawWireframeBoundingBox_nea(
aabb: AxisAlignedBB,
color: Color,
@@ -1153,6 +1165,8 @@ object RenderUtils {
GlStateManager.disableTexture2D()
GlStateManager.disableCull()
val vp = getViewerPos(partialTicks)
+
+ @Suppress("ktlint:standard:argument-list-wrapping")
val effectiveAABB = AxisAlignedBB(
aabb.minX - vp.x, aabb.minY - vp.y, aabb.minZ - vp.z,
aabb.maxX - vp.x, aabb.maxY - vp.y, aabb.maxZ - vp.z,
@@ -1220,7 +1234,7 @@ object RenderUtils {
middlePoint,
sidePoint1,
sidePoint2,
- c
+ c,
)
}
@@ -1238,9 +1252,10 @@ object RenderUtils {
if (path.isEmpty()) return
val points = if (startAtEye) {
listOf(
- this.exactPlayerEyeLocation()
- + Minecraft.getMinecraft().thePlayer.getLook(this.partialTicks)
- .toLorenzVec()/* .rotateXZ(-Math.PI / 72.0) */.times(2)
+ this.exactPlayerEyeLocation() + Minecraft.getMinecraft().thePlayer.getLook(this.partialTicks)
+ .toLorenzVec()
+ /* .rotateXZ(-Math.PI / 72.0) */
+ .times(2),
)
} else {
emptyList()
@@ -1251,7 +1266,7 @@ object RenderUtils {
colorLine,
lineWidth,
depth,
- bezierPoint
+ bezierPoint,
)
}
path.filter { it.name?.isNotEmpty() == true }.forEach {
@@ -1277,7 +1292,7 @@ object RenderUtils {
if (index != pathLines.lastIndex) it.second - reduce else it.second,
color,
lineWidth,
- depth
+ depth,
)
}
path.zipWithNext3().forEach {
@@ -1328,8 +1343,12 @@ object RenderUtils {
}
bezier2Buffer.flip()
GL11.glMap1f(
- GL11.GL_MAP1_VERTEX_3, 0.0f, 1.0f, 3, 3,
- bezier2Buffer
+ GL11.GL_MAP1_VERTEX_3,
+ 0.0f,
+ 1.0f,
+ 3,
+ 3,
+ bezier2Buffer,
)
GL11.glEnable(GL11.GL_MAP1_VERTEX_3)
@@ -1475,7 +1494,7 @@ object RenderUtils {
c.red / 255f * 0.8f,
c.green / 255f * 0.8f,
c.blue / 255f * 0.8f,
- c.alpha / 255f * alphaMultiplier
+ c.alpha / 255f * alphaMultiplier,
)
// x
@@ -1497,7 +1516,7 @@ object RenderUtils {
c.red / 255f * 0.9f,
c.green / 255f * 0.9f,
c.blue / 255f * 0.9f,
- c.alpha / 255f * alphaMultiplier
+ c.alpha / 255f * alphaMultiplier,
)
// z
worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION)
@@ -1525,7 +1544,7 @@ object RenderUtils {
colour: Color,
depth: Boolean,
) {
- val (cornerOne, cornerTwo, cornerThree, cornerFour, ) = boundingBox.getCorners(boundingBox.maxY)
+ val (cornerOne, cornerTwo, cornerThree, cornerFour) = boundingBox.getCorners(boundingBox.maxY)
this.draw3DLine(cornerOne, cornerTwo, colour, lineWidth, depth)
this.draw3DLine(cornerTwo, cornerThree, colour, lineWidth, depth)
this.draw3DLine(cornerThree, cornerFour, colour, lineWidth, depth)
@@ -1536,7 +1555,6 @@ object RenderUtils {
fun LorenzRenderWorldEvent.draw3DLine_nea(
p1: LorenzVec, p2: LorenzVec, color: Color, lineWidth: Int, depth: Boolean,
) {
- GlStateManager.disableDepth()
GlStateManager.disableCull()
val render = Minecraft.getMinecraft().renderViewEntity
@@ -1571,7 +1589,7 @@ object RenderUtils {
GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f)
GlStateManager.popMatrix()
GlStateManager.disableLighting()
- GlStateManager.enableDepth()
+ GlStateManager.enableCull()
}
fun chromaColor(
@@ -1585,8 +1603,8 @@ object RenderUtils {
Color.HSBtoRGB(
((offset + timeOverride / timeTillRepeat.toDouble(DurationUnit.MILLISECONDS)) % 1).toFloat(),
saturation,
- brightness
- )
+ brightness,
+ ),
)
}
@@ -1722,7 +1740,7 @@ object RenderUtils {
x + width + borderAdjustment,
y + height + borderAdjustment,
topColor,
- bottomColor
+ bottomColor,
)
ShaderManager.disableShader()
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt
index 3ddeb498c..f180e6bf7 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/StringUtils.kt
@@ -230,20 +230,22 @@ object StringUtils {
fun String.removeWordsAtEnd(i: Int) = split(" ").dropLast(i).joinToString(" ")
- fun String.splitLines(width: Int): String {
- val fr = Minecraft.getMinecraft().fontRendererObj
- return GuiUtilRenderComponents.splitText(
- ChatComponentText(this), width, fr, false, false
- ).joinToString("\n") {
- val text = it.formattedText
- val formatCode = Regex("(?:§[a-f0-9l-or]|\\s)*")
- formatCode.matchAt(text, 0)?.let { matcher ->
- val codes = matcher.value.replace("\\s".toRegex(), "")
- codes + text.removeRange(matcher.range)
- } ?: text
- }
+ fun String.splitLines(width: Int): String = GuiUtilRenderComponents.splitText(
+ ChatComponentText(this),
+ width,
+ Minecraft.getMinecraft().fontRendererObj,
+ false,
+ false,
+ ).joinToString("\n") {
+ val text = it.formattedText
+ val formatCode = Regex("(?:§[a-f0-9l-or]|\\s)*")
+ formatCode.matchAt(text, 0)?.let { matcher ->
+ val codes = matcher.value.replace("\\s".toRegex(), "")
+ codes + text.removeRange(matcher.range)
+ } ?: text
}
+
/**
* Creates a comma-separated list using natural formatting (a, b, and c).
* @param list - the list of strings to join into a string, containing 0 or more elements.
@@ -381,6 +383,10 @@ object StringUtils {
fun generateRandomId() = UUID.randomUUID().toString()
+ fun String.insert(pos: Int, chars: CharSequence): String = this.substring(0, pos) + chars + this.substring(pos)
+
+ fun String.insert(pos: Int, char: Char): String = this.substring(0, pos) + char + this.substring(pos)
+
fun replaceIfNeeded(
original: ChatComponentText,
newText: String,