aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/util')
-rw-r--r--src/main/kotlin/util/accessors/chathud.kt8
-rw-r--r--src/main/kotlin/util/mc/InventoryUtil.kt28
-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.kt54
-rw-r--r--src/main/kotlin/util/skyblock/SackUtil.kt110
-rw-r--r--src/main/kotlin/util/textutil.kt184
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))
+ }
+ }
}