diff options
author | Linnea Gräf <nea@nea.moe> | 2024-10-01 18:00:43 +0200 |
---|---|---|
committer | Linnea Gräf <nea@nea.moe> | 2024-10-01 18:00:43 +0200 |
commit | a4eac70118fc25334c9352712fe3c7944b8bed1d (patch) | |
tree | e0faedfca323e243a64a427d13b4fb31bcea9256 /src/main/kotlin/util | |
parent | beb14d73bd2d0d48513e540ff629b48f89b95ed9 (diff) | |
download | firmament-a4eac70118fc25334c9352712fe3c7944b8bed1d.tar.gz firmament-a4eac70118fc25334c9352712fe3c7944b8bed1d.tar.bz2 firmament-a4eac70118fc25334c9352712fe3c7944b8bed1d.zip |
Add basic sack util
[no changelog]
Diffstat (limited to 'src/main/kotlin/util')
-rw-r--r-- | src/main/kotlin/util/accessors/chathud.kt | 8 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/InventoryUtil.kt | 28 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/ItemUtil.kt (renamed from src/main/kotlin/util/ItemUtil.kt) | 8 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/NbtItemData.kt (renamed from src/main/kotlin/util/item/NbtItemData.kt) | 4 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/SkullItemData.kt (renamed from src/main/kotlin/util/item/SkullItemData.kt) | 4 | ||||
-rw-r--r-- | src/main/kotlin/util/regex.kt | 54 | ||||
-rw-r--r-- | src/main/kotlin/util/skyblock/SackUtil.kt | 110 | ||||
-rw-r--r-- | src/main/kotlin/util/textutil.kt | 184 |
8 files changed, 267 insertions, 133 deletions
diff --git a/src/main/kotlin/util/accessors/chathud.kt b/src/main/kotlin/util/accessors/chathud.kt new file mode 100644 index 0000000..effac7d --- /dev/null +++ b/src/main/kotlin/util/accessors/chathud.kt @@ -0,0 +1,8 @@ +package moe.nea.firmament.util.accessors + +import net.minecraft.client.gui.hud.ChatHud +import net.minecraft.client.gui.hud.ChatHudLine +import moe.nea.firmament.mixins.accessor.AccessorChatHud + +val ChatHud.messages: MutableList<ChatHudLine> + get() = (this as AccessorChatHud).messages_firmament diff --git a/src/main/kotlin/util/mc/InventoryUtil.kt b/src/main/kotlin/util/mc/InventoryUtil.kt new file mode 100644 index 0000000..74f7b9f --- /dev/null +++ b/src/main/kotlin/util/mc/InventoryUtil.kt @@ -0,0 +1,28 @@ +package moe.nea.firmament.util.mc + +import java.util.Spliterator +import java.util.Spliterators +import net.minecraft.inventory.Inventory +import net.minecraft.item.ItemStack + +val Inventory.indices get() = 0 until size() +val Inventory.iterableView + get() = object : Iterable<ItemStack> { + override fun spliterator(): Spliterator<ItemStack> { + return Spliterators.spliterator(iterator(), size().toLong(), 0) + } + + override fun iterator(): Iterator<ItemStack> { + return object : Iterator<ItemStack> { + var i = 0 + override fun hasNext(): Boolean { + return i < size() + } + + override fun next(): ItemStack { + if (!hasNext()) throw NoSuchElementException() + return getStack(i++) + } + } + } + } diff --git a/src/main/kotlin/util/ItemUtil.kt b/src/main/kotlin/util/mc/ItemUtil.kt index 40d6198..13519cf 100644 --- a/src/main/kotlin/util/ItemUtil.kt +++ b/src/main/kotlin/util/mc/ItemUtil.kt @@ -1,13 +1,7 @@ - - -package moe.nea.firmament.util +package moe.nea.firmament.util.mc 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 diff --git a/src/main/kotlin/util/item/NbtItemData.kt b/src/main/kotlin/util/mc/NbtItemData.kt index f7f259d..e8a908f 100644 --- a/src/main/kotlin/util/item/NbtItemData.kt +++ b/src/main/kotlin/util/mc/NbtItemData.kt @@ -1,6 +1,4 @@ - - -package moe.nea.firmament.util.item +package moe.nea.firmament.util.mc import net.minecraft.component.DataComponentTypes import net.minecraft.component.type.LoreComponent diff --git a/src/main/kotlin/util/item/SkullItemData.kt b/src/main/kotlin/util/mc/SkullItemData.kt index ddab88e..0405b65 100644 --- a/src/main/kotlin/util/item/SkullItemData.kt +++ b/src/main/kotlin/util/mc/SkullItemData.kt @@ -1,8 +1,6 @@ - - @file:UseSerializers(DashlessUUIDSerializer::class, InstantAsLongSerializer::class) -package moe.nea.firmament.util.item +package moe.nea.firmament.util.mc import com.mojang.authlib.GameProfile import com.mojang.authlib.minecraft.MinecraftProfileTexture diff --git a/src/main/kotlin/util/regex.kt b/src/main/kotlin/util/regex.kt index 3ce5bd8..78c90e8 100644 --- a/src/main/kotlin/util/regex.kt +++ b/src/main/kotlin/util/regex.kt @@ -1,5 +1,3 @@ - - package moe.nea.firmament.util import java.util.regex.Matcher @@ -10,12 +8,12 @@ import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds inline fun <T> String.ifMatches(regex: Regex, block: (MatchResult) -> T): T? = - regex.matchEntire(this)?.let(block) + regex.matchEntire(this)?.let(block) inline fun <T> Pattern.useMatch(string: String, block: Matcher.() -> T): T? = - matcher(string) - .takeIf(Matcher::matches) - ?.let(block) + matcher(string) + .takeIf(Matcher::matches) + ?.let(block) @Language("RegExp") val TIME_PATTERN = "[0-9]+[ms]" @@ -25,31 +23,33 @@ val SHORT_NUMBER_FORMAT = "[0-9]+(?:,[0-9]+)*(?:\\.[0-9]+)?[kKmMbB]?" val siScalars = mapOf( - 'k' to 1_000.0, - 'K' to 1_000.0, - 'm' to 1_000_000.0, - 'M' to 1_000_000.0, - 'b' to 1_000_000_000.0, - 'B' to 1_000_000_000.0, + 'k' to 1_000.0, + 'K' to 1_000.0, + 'm' to 1_000_000.0, + 'M' to 1_000_000.0, + 'b' to 1_000_000_000.0, + 'B' to 1_000_000_000.0, ) fun parseTimePattern(text: String): Duration { - val length = text.dropLast(1).toInt() - return when (text.last()) { - 'm' -> length.minutes - 's' -> length.seconds - else -> error("Invalid pattern for time $text") - } + val length = text.dropLast(1).toInt() + return when (text.last()) { + 'm' -> length.minutes + 's' -> length.seconds + else -> error("Invalid pattern for time $text") + } } fun parseShortNumber(string: String): Double { - var k = string.replace(",", "") - val scalar = k.last() - var scalarMultiplier = siScalars[scalar] - if (scalarMultiplier == null) { - scalarMultiplier = 1.0 - } else { - k = k.dropLast(1) - } - return k.toDouble() * scalarMultiplier + if (string.startsWith("-")) return -parseShortNumber(string.substring(1)) + if (string.startsWith("+")) return parseShortNumber(string.substring(1)) + var k = string.replace(",", "") + val scalar = k.last() + var scalarMultiplier = siScalars[scalar] + if (scalarMultiplier == null) { + scalarMultiplier = 1.0 + } else { + k = k.dropLast(1) + } + return k.toDouble() * scalarMultiplier } diff --git a/src/main/kotlin/util/skyblock/SackUtil.kt b/src/main/kotlin/util/skyblock/SackUtil.kt new file mode 100644 index 0000000..2679949 --- /dev/null +++ b/src/main/kotlin/util/skyblock/SackUtil.kt @@ -0,0 +1,110 @@ +package moe.nea.firmament.util.skyblock + +import kotlinx.serialization.Serializable +import kotlinx.serialization.serializer +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen +import net.minecraft.text.HoverEvent +import net.minecraft.text.Text +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.ChestInventoryUpdateEvent +import moe.nea.firmament.events.ProcessChatEvent +import moe.nea.firmament.repo.ItemNameLookup +import moe.nea.firmament.util.SHORT_NUMBER_FORMAT +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.data.ProfileSpecificDataHolder +import moe.nea.firmament.util.mc.displayNameAccordingToNbt +import moe.nea.firmament.util.mc.iterableView +import moe.nea.firmament.util.mc.loreAccordingToNbt +import moe.nea.firmament.util.parseShortNumber +import moe.nea.firmament.util.skyBlockId +import moe.nea.firmament.util.unformattedString +import moe.nea.firmament.util.useMatch + +object SackUtil { + @Serializable + data class SackContents( + // TODO: store the certainty of knowledge for each item. + val contents: MutableMap<SkyblockId, Long> = mutableMapOf(), +// val sackTypes: + ) + + object Store : ProfileSpecificDataHolder<SackContents>(serializer(), "Sacks", ::SackContents) + + val items get() = Store.data?.contents ?: mutableMapOf() + val storedRegex = "^Stored: (?<stored>$SHORT_NUMBER_FORMAT)/(?<max>$SHORT_NUMBER_FORMAT)$".toPattern() + + @Subscribe + fun storeDataFromInventory(event: ChestInventoryUpdateEvent) { + val screen = event.inventory as? GenericContainerScreen ?: return + if (!screen.title.unformattedString.endsWith(" Sack")) return + val inv = screen.screenHandler?.inventory ?: return + if (inv.size() < 18) return + val backSlot = inv.getStack(inv.size() - 5) + if (backSlot.displayNameAccordingToNbt.unformattedString != "Go Back") return + if (backSlot.loreAccordingToNbt.map { it.unformattedString } != listOf("To Sack of Sacks")) return + for (itemStack in inv.iterableView) { + // TODO: handle runes and gemstones + val stored = itemStack.loreAccordingToNbt.firstNotNullOfOrNull { + storedRegex.useMatch(it.unformattedString) { + val stored = parseShortNumber(group("stored")).toLong() + val max = parseShortNumber(group("max")).toLong() + stored + } + } ?: continue + val itemId = itemStack.skyBlockId ?: continue + items[itemId] = stored + } + Store.markDirty() + } + + @Subscribe + fun updateFromChat(event: ProcessChatEvent) { + if (!event.unformattedString.startsWith("[Sacks]")) return + val update = ChatUpdate() + event.text.siblings.forEach(update::updateFromHoverText) + } + + data class SackUpdate( + val itemId: SkyblockId?, + val itemName: String, + val changeAmount: Long, + ) + + private class ChatUpdate { + val updates = mutableListOf<SackUpdate>() + var foundAdded = false + var foundRemoved = false + + fun updateFromCleanText(cleanedText: String) { + cleanedText.split("\n").forEach { line -> + changePattern.useMatch(line) { + val amount = parseShortNumber(group("amount")).toLong() + val itemName = group("itemName") + val itemId = ItemNameLookup.guessItemByName(itemName, false) + updates.add(SackUpdate(itemId, itemName, amount)) + } + } + } + + fun updateFromHoverText(text: Text) { + text.siblings.forEach(::updateFromHoverText) + val hoverText = text.style.hoverEvent?.getValue(HoverEvent.Action.SHOW_TEXT) ?: return + val cleanedText = hoverText.unformattedString + if (cleanedText.startsWith("Added items:\n")) { + if (!foundAdded) { + updateFromCleanText(cleanedText) + foundAdded = true + } + } + if (cleanedText.startsWith("Removed items:\n")) { + if (!foundRemoved) { + updateFromCleanText(cleanedText) + foundRemoved = true + } + } + } + + } + + val changePattern = " (?<amount>[+\\-]$SHORT_NUMBER_FORMAT) (?<itemName>[^(]+) \\(.*\\)".toPattern() +} diff --git a/src/main/kotlin/util/textutil.kt b/src/main/kotlin/util/textutil.kt index a05733c..36924a6 100644 --- a/src/main/kotlin/util/textutil.kt +++ b/src/main/kotlin/util/textutil.kt @@ -1,10 +1,7 @@ - - package moe.nea.firmament.util import net.minecraft.text.MutableText import net.minecraft.text.PlainTextContent -import net.minecraft.text.Style import net.minecraft.text.Text import net.minecraft.text.TranslatableTextContent import net.minecraft.util.Formatting @@ -12,106 +9,107 @@ import moe.nea.firmament.Firmament class TextMatcher(text: Text) { - data class State( - var iterator: MutableList<Text>, - var currentText: Text?, - var offset: Int, - var textContent: String, - ) - - var state = State( - mutableListOf(text), - null, - 0, - "" - ) - - fun pollChunk(): Boolean { - val firstOrNull = state.iterator.removeFirstOrNull() ?: return false - state.offset = 0 - state.currentText = firstOrNull - state.textContent = when (val content = firstOrNull.content) { - is PlainTextContent.Literal -> content.string - else -> { - Firmament.logger.warn("TextContent of type ${content.javaClass} not understood.") - return false - } - } - state.iterator.addAll(0, firstOrNull.siblings) - return true - } - - fun pollChunks(): Boolean { - while (state.offset !in state.textContent.indices) { - if (!pollChunk()) { - return false - } - } - return true - } - - fun pollChar(): Char? { - if (!pollChunks()) return null - return state.textContent[state.offset++] - } - - - fun expectString(string: String): Boolean { - var found = "" - while (found.length < string.length) { - if (!pollChunks()) return false - val takeable = state.textContent.drop(state.offset).take(string.length - found.length) - state.offset += takeable.length - found += takeable - } - return found == string - } + data class State( + var iterator: MutableList<Text>, + var currentText: Text?, + var offset: Int, + var textContent: String, + ) + + var state = State( + mutableListOf(text), + null, + 0, + "" + ) + + fun pollChunk(): Boolean { + val firstOrNull = state.iterator.removeFirstOrNull() ?: return false + state.offset = 0 + state.currentText = firstOrNull + state.textContent = when (val content = firstOrNull.content) { + is PlainTextContent.Literal -> content.string + else -> { + Firmament.logger.warn("TextContent of type ${content.javaClass} not understood.") + return false + } + } + state.iterator.addAll(0, firstOrNull.siblings) + return true + } + + fun pollChunks(): Boolean { + while (state.offset !in state.textContent.indices) { + if (!pollChunk()) { + return false + } + } + return true + } + + fun pollChar(): Char? { + if (!pollChunks()) return null + return state.textContent[state.offset++] + } + + + fun expectString(string: String): Boolean { + var found = "" + while (found.length < string.length) { + if (!pollChunks()) return false + val takeable = state.textContent.drop(state.offset).take(string.length - found.length) + state.offset += takeable.length + found += takeable + } + return found == string + } } val formattingChars = "kmolnrKMOLNR".toSet() fun CharSequence.removeColorCodes(keepNonColorCodes: Boolean = false): String { - var nextParagraph = indexOf('§') - if (nextParagraph < 0) return this.toString() - val stringBuffer = StringBuilder(this.length) - var readIndex = 0 - while (nextParagraph >= 0) { - stringBuffer.append(this, readIndex, nextParagraph) - if (keepNonColorCodes && nextParagraph + 1 < length && this[nextParagraph + 1] in formattingChars) { - readIndex = nextParagraph - nextParagraph = indexOf('§', startIndex = readIndex + 1) - } else { - readIndex = nextParagraph + 2 - nextParagraph = indexOf('§', startIndex = readIndex) - } - if (readIndex > this.length) - readIndex = this.length - } - stringBuffer.append(this, readIndex, this.length) - return stringBuffer.toString() + var nextParagraph = indexOf('§') + if (nextParagraph < 0) return this.toString() + val stringBuffer = StringBuilder(this.length) + var readIndex = 0 + while (nextParagraph >= 0) { + stringBuffer.append(this, readIndex, nextParagraph) + if (keepNonColorCodes && nextParagraph + 1 < length && this[nextParagraph + 1] in formattingChars) { + readIndex = nextParagraph + nextParagraph = indexOf('§', startIndex = readIndex + 1) + } else { + readIndex = nextParagraph + 2 + nextParagraph = indexOf('§', startIndex = readIndex) + } + if (readIndex > this.length) + readIndex = this.length + } + stringBuffer.append(this, readIndex, this.length) + return stringBuffer.toString() } val Text.unformattedString: String - get() = string.removeColorCodes() + get() = string.removeColorCodes() +fun Text.allSiblings(): List<Text> = listOf(this) + siblings.flatMap { it.allSiblings() } fun MutableText.withColor(formatting: Formatting) = this.styled { it.withColor(formatting).withItalic(false) } fun Text.transformEachRecursively(function: (Text) -> Text): Text { - val c = this.content - if (c is TranslatableTextContent) { - return Text.translatableWithFallback(c.key, c.fallback, *c.args.map { - (if (it is Text) it else Text.literal(it.toString())).transformEachRecursively(function) - }.toTypedArray()).also { new -> - new.style = this.style - new.siblings.clear() - this.siblings.forEach { child -> - new.siblings.add(child.transformEachRecursively(function)) - } - } - } - return function(this.copy().also { it.siblings.clear() }).also { tt -> - this.siblings.forEach { - tt.siblings.add(it.transformEachRecursively(function)) - } - } + val c = this.content + if (c is TranslatableTextContent) { + return Text.translatableWithFallback(c.key, c.fallback, *c.args.map { + (if (it is Text) it else Text.literal(it.toString())).transformEachRecursively(function) + }.toTypedArray()).also { new -> + new.style = this.style + new.siblings.clear() + this.siblings.forEach { child -> + new.siblings.add(child.transformEachRecursively(function)) + } + } + } + return function(this.copy().also { it.siblings.clear() }).also { tt -> + this.siblings.forEach { + tt.siblings.add(it.transformEachRecursively(function)) + } + } } |