aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/util
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-08-28 19:04:24 +0200
committerLinnea Gräf <nea@nea.moe>2024-08-28 19:04:24 +0200
commitd2f240ff0ca0d27f417f837e706c781a98c31311 (patch)
tree0db7aff6cc14deaf36eed83889d59fd6b3a6f599 /src/main/kotlin/util
parenta6906308163aa3b2d18fa1dc1aa71ac9bbcc83ab (diff)
downloadFirmament-d2f240ff0ca0d27f417f837e706c781a98c31311.tar.gz
Firmament-d2f240ff0ca0d27f417f837e706c781a98c31311.tar.bz2
Firmament-d2f240ff0ca0d27f417f837e706c781a98c31311.zip
Refactor source layout
Introduce compat source sets and move all kotlin sources to the main directory [no changelog]
Diffstat (limited to 'src/main/kotlin/util')
-rw-r--r--src/main/kotlin/util/Base64Util.kt10
-rw-r--r--src/main/kotlin/util/BazaarPriceStrategy.kt19
-rw-r--r--src/main/kotlin/util/ClipboardUtils.kt24
-rw-r--r--src/main/kotlin/util/CommonSoundEffects.kt26
-rw-r--r--src/main/kotlin/util/DurabilityBarEvent.kt20
-rw-r--r--src/main/kotlin/util/ErrorBoundary.kt10
-rw-r--r--src/main/kotlin/util/FirmFormatters.kt59
-rw-r--r--src/main/kotlin/util/FragmentGuiScreen.kt93
-rw-r--r--src/main/kotlin/util/GetRectangle.kt17
-rw-r--r--src/main/kotlin/util/HoveredItemStack.kt31
-rw-r--r--src/main/kotlin/util/IdentifierSerializer.kt25
-rw-r--r--src/main/kotlin/util/IdentityCharacteristics.kt15
-rw-r--r--src/main/kotlin/util/ItemUtil.kt26
-rw-r--r--src/main/kotlin/util/LegacyFormattingCode.kt35
-rw-r--r--src/main/kotlin/util/LegacyTagParser.kt245
-rw-r--r--src/main/kotlin/util/LoadResource.kt20
-rw-r--r--src/main/kotlin/util/Locraw.kt12
-rw-r--r--src/main/kotlin/util/LogIfNull.kt8
-rw-r--r--src/main/kotlin/util/MC.kt94
-rw-r--r--src/main/kotlin/util/MinecraftDispatcher.kt8
-rw-r--r--src/main/kotlin/util/MoulConfigFragment.kt44
-rw-r--r--src/main/kotlin/util/MoulConfigUtils.kt230
-rw-r--r--src/main/kotlin/util/MutableMapWithMaxSize.kt38
-rw-r--r--src/main/kotlin/util/SBData.kt66
-rw-r--r--src/main/kotlin/util/ScoreboardUtil.kt45
-rw-r--r--src/main/kotlin/util/ScreenUtil.kt38
-rw-r--r--src/main/kotlin/util/SequenceUtil.kt11
-rw-r--r--src/main/kotlin/util/SkyBlockIsland.kt42
-rw-r--r--src/main/kotlin/util/SkyblockId.kt149
-rw-r--r--src/main/kotlin/util/SortedMapSerializer.kt25
-rw-r--r--src/main/kotlin/util/TemplateUtil.kt85
-rw-r--r--src/main/kotlin/util/TimeMark.kt44
-rw-r--r--src/main/kotlin/util/Timer.kt25
-rw-r--r--src/main/kotlin/util/WarpUtil.kt75
-rw-r--r--src/main/kotlin/util/assertions.kt25
-rw-r--r--src/main/kotlin/util/async/input.kt47
-rw-r--r--src/main/kotlin/util/colorconversion.kt13
-rw-r--r--src/main/kotlin/util/customgui/CoordRememberingSlot.kt14
-rw-r--r--src/main/kotlin/util/customgui/CustomGui.kt72
-rw-r--r--src/main/kotlin/util/customgui/HasCustomGui.kt17
-rw-r--r--src/main/kotlin/util/data/DataHolder.kt62
-rw-r--r--src/main/kotlin/util/data/IDataHolder.kt77
-rw-r--r--src/main/kotlin/util/data/ProfileSpecificDataHolder.kt84
-rw-r--r--src/main/kotlin/util/filter/IteratorFilterSet.kt33
-rw-r--r--src/main/kotlin/util/item/NbtItemData.kt24
-rw-r--r--src/main/kotlin/util/item/SkullItemData.kt90
-rw-r--r--src/main/kotlin/util/json/BlockPosSerializer.kt25
-rw-r--r--src/main/kotlin/util/json/DashlessUUIDSerializer.kt29
-rw-r--r--src/main/kotlin/util/json/InstantAsLongSerializer.kt22
-rw-r--r--src/main/kotlin/util/json/SingletonSerializableList.kt31
-rw-r--r--src/main/kotlin/util/listutil.kt9
-rw-r--r--src/main/kotlin/util/propertyutil.kt9
-rw-r--r--src/main/kotlin/util/regex.kt55
-rw-r--r--src/main/kotlin/util/render/FacingThePlayerContext.kt101
-rw-r--r--src/main/kotlin/util/render/LerpUtils.kt33
-rw-r--r--src/main/kotlin/util/render/RenderCircleProgress.kt95
-rw-r--r--src/main/kotlin/util/render/RenderContextDSL.kt6
-rw-r--r--src/main/kotlin/util/render/RenderInWorldContext.kt294
-rw-r--r--src/main/kotlin/util/render/TranslatedScissors.kt22
-rw-r--r--src/main/kotlin/util/stringutil.kt6
-rw-r--r--src/main/kotlin/util/textutil.kt117
-rw-r--r--src/main/kotlin/util/uuid.kt12
62 files changed, 3138 insertions, 0 deletions
diff --git a/src/main/kotlin/util/Base64Util.kt b/src/main/kotlin/util/Base64Util.kt
new file mode 100644
index 0000000..44bcdfd
--- /dev/null
+++ b/src/main/kotlin/util/Base64Util.kt
@@ -0,0 +1,10 @@
+
+package moe.nea.firmament.util
+
+object Base64Util {
+ fun String.padToValidBase64(): String {
+ val align = this.length % 4
+ if (align == 0) return this
+ return this + "=".repeat(4 - align)
+ }
+}
diff --git a/src/main/kotlin/util/BazaarPriceStrategy.kt b/src/main/kotlin/util/BazaarPriceStrategy.kt
new file mode 100644
index 0000000..002eedb
--- /dev/null
+++ b/src/main/kotlin/util/BazaarPriceStrategy.kt
@@ -0,0 +1,19 @@
+
+package moe.nea.firmament.util
+
+import moe.nea.firmament.repo.HypixelStaticData
+
+enum class BazaarPriceStrategy {
+ BUY_ORDER,
+ SELL_ORDER,
+ NPC_SELL;
+
+ fun getSellPrice(skyblockId: SkyblockId): Double {
+ val bazaarEntry = HypixelStaticData.bazaarData[skyblockId] ?: return 0.0
+ return when (this) {
+ BUY_ORDER -> bazaarEntry.quickStatus.sellPrice
+ SELL_ORDER -> bazaarEntry.quickStatus.buyPrice
+ NPC_SELL -> TODO()
+ }
+ }
+}
diff --git a/src/main/kotlin/util/ClipboardUtils.kt b/src/main/kotlin/util/ClipboardUtils.kt
new file mode 100644
index 0000000..7b9b836
--- /dev/null
+++ b/src/main/kotlin/util/ClipboardUtils.kt
@@ -0,0 +1,24 @@
+
+
+package moe.nea.firmament.util
+
+import moe.nea.firmament.Firmament
+
+object ClipboardUtils {
+ fun setTextContent(string: String) {
+ try {
+ MC.keyboard.clipboard = string.ifEmpty { " " }
+ } catch (e: Exception) {
+ Firmament.logger.error("Could not write clipboard", e)
+ }
+ }
+
+ fun getTextContents(): String {
+ try {
+ return MC.keyboard.clipboard ?: ""
+ } catch (e: Exception) {
+ Firmament.logger.error("Could not read clipboard", e)
+ return ""
+ }
+ }
+}
diff --git a/src/main/kotlin/util/CommonSoundEffects.kt b/src/main/kotlin/util/CommonSoundEffects.kt
new file mode 100644
index 0000000..a97a2cb
--- /dev/null
+++ b/src/main/kotlin/util/CommonSoundEffects.kt
@@ -0,0 +1,26 @@
+
+
+package moe.nea.firmament.util
+
+import net.minecraft.client.sound.PositionedSoundInstance
+import net.minecraft.sound.SoundEvent
+import net.minecraft.util.Identifier
+
+// TODO: Replace these with custom sound events that just re use the vanilla ogg s
+object CommonSoundEffects {
+ fun playSound(identifier: Identifier) {
+ MC.soundManager.play(PositionedSoundInstance.master(SoundEvent.of(identifier), 1F))
+ }
+
+ fun playFailure() {
+ playSound(Identifier.of("minecraft", "block.anvil.place"))
+ }
+
+ fun playSuccess() {
+ playDing()
+ }
+
+ fun playDing() {
+ playSound(Identifier.of("minecraft", "entity.arrow.hit_player"))
+ }
+}
diff --git a/src/main/kotlin/util/DurabilityBarEvent.kt b/src/main/kotlin/util/DurabilityBarEvent.kt
new file mode 100644
index 0000000..993462c
--- /dev/null
+++ b/src/main/kotlin/util/DurabilityBarEvent.kt
@@ -0,0 +1,20 @@
+
+package moe.nea.firmament.util
+
+import me.shedaniel.math.Color
+import net.minecraft.item.ItemStack
+import moe.nea.firmament.events.FirmamentEvent
+import moe.nea.firmament.events.FirmamentEventBus
+
+data class DurabilityBarEvent(
+ val item: ItemStack,
+) : FirmamentEvent() {
+ data class DurabilityBar(
+ val color: Color,
+ val percentage: Float,
+ )
+
+ var barOverride: DurabilityBar? = null
+
+ companion object : FirmamentEventBus<DurabilityBarEvent>()
+}
diff --git a/src/main/kotlin/util/ErrorBoundary.kt b/src/main/kotlin/util/ErrorBoundary.kt
new file mode 100644
index 0000000..fbc5b37
--- /dev/null
+++ b/src/main/kotlin/util/ErrorBoundary.kt
@@ -0,0 +1,10 @@
+
+
+package moe.nea.firmament.util
+
+
+fun <T> errorBoundary(block: () -> T): T? {
+ // TODO: implement a proper error boundary here to avoid crashing minecraft code
+ return block()
+}
+
diff --git a/src/main/kotlin/util/FirmFormatters.kt b/src/main/kotlin/util/FirmFormatters.kt
new file mode 100644
index 0000000..c3bdd16
--- /dev/null
+++ b/src/main/kotlin/util/FirmFormatters.kt
@@ -0,0 +1,59 @@
+
+
+package moe.nea.firmament.util
+
+import com.google.common.math.IntMath.pow
+import kotlin.math.absoluteValue
+import kotlin.time.Duration
+
+object FirmFormatters {
+ fun formatCommas(int: Int, segments: Int = 3): String = formatCommas(int.toLong(), segments)
+ fun formatCommas(long: Long, segments: Int = 3): String {
+ val α = long / 1000
+ if (α != 0L) {
+ return formatCommas(α, segments) + "," + (long - α * 1000).toString().padStart(3, '0')
+ }
+ return long.toString()
+ }
+
+ fun formatCommas(float: Float, fractionalDigits: Int): String = formatCommas(float.toDouble(), fractionalDigits)
+ fun formatCommas(double: Double, fractionalDigits: Int): String {
+ val long = double.toLong()
+ val δ = (double - long).absoluteValue
+ val μ = pow(10, fractionalDigits)
+ val digits = (μ * δ).toInt().toString().padStart(fractionalDigits, '0').trimEnd('0')
+ return formatCommas(long) + (if (digits.isEmpty()) "" else ".$digits")
+ }
+
+ fun formatDistance(distance: Double): String {
+ if (distance < 10)
+ return "%.1fm".format(distance)
+ return "%dm".format(distance.toInt())
+ }
+
+ fun formatTimespan(duration: Duration, millis: Boolean = false): String {
+ if (duration.isInfinite()) {
+ return if (duration.isPositive()) "∞"
+ else "-∞"
+ }
+ val sb = StringBuilder()
+ if (duration.isNegative()) sb.append("-")
+ duration.toComponents { days, hours, minutes, seconds, nanoseconds ->
+ if (days > 0) {
+ sb.append(days).append("d")
+ }
+ if (hours > 0) {
+ sb.append(hours).append("h")
+ }
+ if (minutes > 0) {
+ sb.append(minutes).append("m")
+ }
+ sb.append(seconds).append("s")
+ if (millis) {
+ sb.append(nanoseconds / 1_000_000).append("ms")
+ }
+ }
+ return sb.toString()
+ }
+
+}
diff --git a/src/main/kotlin/util/FragmentGuiScreen.kt b/src/main/kotlin/util/FragmentGuiScreen.kt
new file mode 100644
index 0000000..5e13d51
--- /dev/null
+++ b/src/main/kotlin/util/FragmentGuiScreen.kt
@@ -0,0 +1,93 @@
+
+
+package moe.nea.firmament.util
+
+import io.github.notenoughupdates.moulconfig.gui.GuiContext
+import me.shedaniel.math.Dimension
+import me.shedaniel.math.Point
+import me.shedaniel.math.Rectangle
+import net.minecraft.client.gui.DrawContext
+import net.minecraft.client.gui.screen.Screen
+import net.minecraft.text.Text
+
+abstract class FragmentGuiScreen(
+ val dismissOnOutOfBounds: Boolean = true
+) : Screen(Text.literal("")) {
+ var popup: MoulConfigFragment? = null
+
+ fun createPopup(context: GuiContext, position: Point) {
+ popup = MoulConfigFragment(context, position) { popup = null }
+ }
+
+ override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
+ super.render(context, mouseX, mouseY, delta)
+ context.matrices.push()
+ context.matrices.translate(0f, 0f, 1000f)
+ popup?.render(context, mouseX, mouseY, delta)
+ context.matrices.pop()
+ }
+
+ private inline fun ifPopup(ifYes: (MoulConfigFragment) -> Unit): Boolean {
+ val p = popup ?: return false
+ ifYes(p)
+ return true
+ }
+
+ override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ return ifPopup {
+ it.keyPressed(keyCode, scanCode, modifiers)
+ }
+ }
+
+ override fun keyReleased(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
+ return ifPopup {
+ it.keyReleased(keyCode, scanCode, modifiers)
+ }
+ }
+
+ override fun mouseMoved(mouseX: Double, mouseY: Double) {
+ ifPopup { it.mouseMoved(mouseX, mouseY) }
+ }
+
+ override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
+ return ifPopup {
+ it.mouseReleased(mouseX, mouseY, button)
+ }
+ }
+
+ override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
+ return ifPopup {
+ it.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
+ }
+ }
+
+ override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
+ return ifPopup {
+ if (!Rectangle(
+ it.position,
+ Dimension(it.context.root.width, it.context.root.height)
+ ).contains(Point(mouseX, mouseY))
+ && dismissOnOutOfBounds
+ ) {
+ popup = null
+ } else {
+ it.mouseClicked(mouseX, mouseY, button)
+ }
+ }|| super.mouseClicked(mouseX, mouseY, button)
+ }
+
+ override fun charTyped(chr: Char, modifiers: Int): Boolean {
+ return ifPopup { it.charTyped(chr, modifiers) }
+ }
+
+ override fun mouseScrolled(
+ mouseX: Double,
+ mouseY: Double,
+ horizontalAmount: Double,
+ verticalAmount: Double
+ ): Boolean {
+ return ifPopup {
+ it.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount)
+ }
+ }
+}
diff --git a/src/main/kotlin/util/GetRectangle.kt b/src/main/kotlin/util/GetRectangle.kt
new file mode 100644
index 0000000..ec64f31
--- /dev/null
+++ b/src/main/kotlin/util/GetRectangle.kt
@@ -0,0 +1,17 @@
+
+
+package moe.nea.firmament.util
+
+import me.shedaniel.math.Rectangle
+import moe.nea.firmament.mixins.accessor.AccessorHandledScreen
+import net.minecraft.client.gui.screen.ingame.HandledScreen
+
+fun HandledScreen<*>.getRectangle(): Rectangle {
+ this as AccessorHandledScreen
+ return Rectangle(
+ getX_Firmament(),
+ getY_Firmament(),
+ getBackgroundWidth_Firmament(),
+ getBackgroundHeight_Firmament()
+ )
+}
diff --git a/src/main/kotlin/util/HoveredItemStack.kt b/src/main/kotlin/util/HoveredItemStack.kt
new file mode 100644
index 0000000..47a59d0
--- /dev/null
+++ b/src/main/kotlin/util/HoveredItemStack.kt
@@ -0,0 +1,31 @@
+
+
+package moe.nea.firmament.util
+
+import me.shedaniel.math.impl.PointHelper
+import me.shedaniel.rei.api.client.REIRuntime
+import me.shedaniel.rei.api.client.gui.widgets.Slot
+import me.shedaniel.rei.api.client.registry.screen.ScreenRegistry
+import net.minecraft.client.gui.Element
+import net.minecraft.client.gui.ParentElement
+import net.minecraft.client.gui.screen.ingame.HandledScreen
+import net.minecraft.item.ItemStack
+import moe.nea.firmament.mixins.accessor.AccessorHandledScreen
+
+
+val HandledScreen<*>.focusedItemStack: ItemStack?
+ get() {
+ this as AccessorHandledScreen
+ val vanillaSlot = this.focusedSlot_Firmament?.stack
+ if (vanillaSlot != null) return vanillaSlot
+ val focusedSlot = ScreenRegistry.getInstance().getFocusedStack(this, PointHelper.ofMouse())
+ if (focusedSlot != null) return focusedSlot.cheatsAs().value
+ var baseElement: Element? = REIRuntime.getInstance().overlay.orElse(null)
+ val mx = PointHelper.getMouseFloatingX()
+ val my = PointHelper.getMouseFloatingY()
+ while (true) {
+ if (baseElement is Slot) return baseElement.currentEntry.cheatsAs().value
+ if (baseElement !is ParentElement) return null
+ baseElement = baseElement.hoveredElement(mx, my).orElse(null)
+ }
+ }
diff --git a/src/main/kotlin/util/IdentifierSerializer.kt b/src/main/kotlin/util/IdentifierSerializer.kt
new file mode 100644
index 0000000..65c5b1c
--- /dev/null
+++ b/src/main/kotlin/util/IdentifierSerializer.kt
@@ -0,0 +1,25 @@
+
+package moe.nea.firmament.util
+
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.builtins.serializer
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import net.minecraft.util.Identifier
+
+object IdentifierSerializer : KSerializer<Identifier> {
+ val delegateSerializer = String.serializer()
+ override val descriptor: SerialDescriptor
+ get() = PrimitiveSerialDescriptor("Identifier", PrimitiveKind.STRING)
+
+ override fun deserialize(decoder: Decoder): Identifier {
+ return Identifier.of(decoder.decodeSerializableValue(delegateSerializer))
+ }
+
+ override fun serialize(encoder: Encoder, value: Identifier) {
+ encoder.encodeSerializableValue(delegateSerializer, value.toString())
+ }
+}
diff --git a/src/main/kotlin/util/IdentityCharacteristics.kt b/src/main/kotlin/util/IdentityCharacteristics.kt
new file mode 100644
index 0000000..f6054c4
--- /dev/null
+++ b/src/main/kotlin/util/IdentityCharacteristics.kt
@@ -0,0 +1,15 @@
+
+
+package moe.nea.firmament.util
+
+class IdentityCharacteristics<T>(val value: T) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is IdentityCharacteristics<*>) return false
+ return value === other.value
+ }
+
+ override fun hashCode(): Int {
+ return System.identityHashCode(value)
+ }
+}
diff --git a/src/main/kotlin/util/ItemUtil.kt b/src/main/kotlin/util/ItemUtil.kt
new file mode 100644
index 0000000..40d6198
--- /dev/null
+++ b/src/main/kotlin/util/ItemUtil.kt
@@ -0,0 +1,26 @@
+
+
+package moe.nea.firmament.util
+
+import net.minecraft.item.ItemStack
+import net.minecraft.nbt.NbtCompound
+import net.minecraft.nbt.NbtList
+import net.minecraft.text.Text
+import moe.nea.firmament.util.item.loreAccordingToNbt
+
+
+fun ItemStack.appendLore(args: List<Text>) {
+ if (args.isEmpty()) return
+ modifyLore {
+ val loreList = loreAccordingToNbt.toMutableList()
+ for (arg in args) {
+ loreList.add(arg)
+ }
+ loreList
+ }
+}
+
+fun ItemStack.modifyLore(update: (List<Text>) -> List<Text>) {
+ val loreList = loreAccordingToNbt
+ loreAccordingToNbt = update(loreList)
+}
diff --git a/src/main/kotlin/util/LegacyFormattingCode.kt b/src/main/kotlin/util/LegacyFormattingCode.kt
new file mode 100644
index 0000000..44bacfc
--- /dev/null
+++ b/src/main/kotlin/util/LegacyFormattingCode.kt
@@ -0,0 +1,35 @@
+
+
+package moe.nea.firmament.util
+
+import net.minecraft.util.Formatting
+
+enum class LegacyFormattingCode(val label: String, val char: Char, val index: Int) {
+ BLACK("BLACK", '0', 0),
+ DARK_BLUE("DARK_BLUE", '1', 1),
+ DARK_GREEN("DARK_GREEN", '2', 2),
+ DARK_AQUA("DARK_AQUA", '3', 3),
+ DARK_RED("DARK_RED", '4', 4),
+ DARK_PURPLE("DARK_PURPLE", '5', 5),
+ GOLD("GOLD", '6', 6),
+ GRAY("GRAY", '7', 7),
+ DARK_GRAY("DARK_GRAY", '8', 8),
+ BLUE("BLUE", '9', 9),
+ GREEN("GREEN", 'a', 10),
+ AQUA("AQUA", 'b', 11),
+ RED("RED", 'c', 12),
+ LIGHT_PURPLE("LIGHT_PURPLE", 'd', 13),
+ YELLOW("YELLOW", 'e', 14),
+ WHITE("WHITE", 'f', 15),
+ OBFUSCATED("OBFUSCATED", 'k', -1),
+ BOLD("BOLD", 'l', -1),
+ STRIKETHROUGH("STRIKETHROUGH", 'm', -1),
+ UNDERLINE("UNDERLINE", 'n', -1),
+ ITALIC("ITALIC", 'o', -1),
+ RESET("RESET", 'r', -1);
+
+ val modern = Formatting.byCode(char)!!
+
+ val formattingCode = "§$char"
+
+}
diff --git a/src/main/kotlin/util/LegacyTagParser.kt b/src/main/kotlin/util/LegacyTagParser.kt
new file mode 100644
index 0000000..4e08da1
--- /dev/null
+++ b/src/main/kotlin/util/LegacyTagParser.kt
@@ -0,0 +1,245 @@
+
+
+package moe.nea.firmament.util
+
+import java.util.*
+import net.minecraft.nbt.AbstractNbtNumber
+import net.minecraft.nbt.NbtByte
+import net.minecraft.nbt.NbtCompound
+import net.minecraft.nbt.NbtDouble
+import net.minecraft.nbt.NbtElement
+import net.minecraft.nbt.NbtFloat
+import net.minecraft.nbt.NbtInt
+import net.minecraft.nbt.NbtList
+import net.minecraft.nbt.NbtLong
+import net.minecraft.nbt.NbtShort
+import net.minecraft.nbt.NbtString
+
+class LegacyTagParser private constructor(string: String) {
+ data class TagParsingException(val baseString: String, val offset: Int, val mes0: String) :
+ Exception("$mes0 at $offset in `$baseString`.")
+
+ class StringRacer(val backing: String) {
+ var idx = 0
+ val stack = Stack<Int>()
+
+ fun pushState() {
+ stack.push(idx)
+ }
+
+ fun popState() {
+ idx = stack.pop()
+ }
+
+ fun discardState() {
+ stack.pop()
+ }
+
+ fun peek(count: Int): String {
+ return backing.substring(minOf(idx, backing.length), minOf(idx + cou