aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/util
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-10-13 17:32:10 +0200
committerLinnea Gräf <nea@nea.moe>2024-10-13 17:32:10 +0200
commite6142bb93619dee768fc18b87ffdd28558d4bcab (patch)
tree03df79712151f7579cd683c8340741ebb3191822 /src/main/kotlin/util
parentdaa63bd914a2f6c5e9b668abb8474884685ee818 (diff)
downloadFirmament-e6142bb93619dee768fc18b87ffdd28558d4bcab.tar.gz
Firmament-e6142bb93619dee768fc18b87ffdd28558d4bcab.tar.bz2
Firmament-e6142bb93619dee768fc18b87ffdd28558d4bcab.zip
Make pickaxe ability display use AbilityUtils
[no changelog]
Diffstat (limited to 'src/main/kotlin/util')
-rw-r--r--src/main/kotlin/util/ErrorUtil.kt16
-rw-r--r--src/main/kotlin/util/MC.kt131
-rw-r--r--src/main/kotlin/util/TimeMark.kt82
-rw-r--r--src/main/kotlin/util/mc/SNbtFormatter.kt138
-rw-r--r--src/main/kotlin/util/regex.kt14
-rw-r--r--src/main/kotlin/util/skyblock/AbilityUtils.kt138
-rw-r--r--src/main/kotlin/util/textutil.kt2
7 files changed, 419 insertions, 102 deletions
diff --git a/src/main/kotlin/util/ErrorUtil.kt b/src/main/kotlin/util/ErrorUtil.kt
new file mode 100644
index 0000000..4f229af
--- /dev/null
+++ b/src/main/kotlin/util/ErrorUtil.kt
@@ -0,0 +1,16 @@
+package moe.nea.firmament.util
+
+import moe.nea.firmament.Firmament
+
+object ErrorUtil {
+ var aggressiveErrors = run {
+ Thread.currentThread().stackTrace.any { it.className.startsWith("org.junit.") } || Firmament.DEBUG
+ }
+
+ @Suppress("NOTHING_TO_INLINE") // Suppressed since i want the logger to not pick up the ErrorUtil stack-frame
+ inline fun softError(message: String) {
+ if (aggressiveErrors) error(message)
+ else Firmament.logger.error(message)
+ }
+
+}
diff --git a/src/main/kotlin/util/MC.kt b/src/main/kotlin/util/MC.kt
index db8eccb..09aa7aa 100644
--- a/src/main/kotlin/util/MC.kt
+++ b/src/main/kotlin/util/MC.kt
@@ -4,7 +4,9 @@ import io.github.moulberry.repo.data.Coordinate
import java.util.concurrent.ConcurrentLinkedQueue
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.screen.ingame.HandledScreen
+import net.minecraft.client.option.GameOptions
import net.minecraft.client.render.WorldRenderer
+import net.minecraft.item.Item
import net.minecraft.network.packet.c2s.play.CommandExecutionC2SPacket
import net.minecraft.registry.BuiltinRegistries
import net.minecraft.registry.RegistryKeys
@@ -16,79 +18,82 @@ import moe.nea.firmament.events.TickEvent
object MC {
- private val messageQueue = ConcurrentLinkedQueue<Text>()
+ private val messageQueue = ConcurrentLinkedQueue<Text>()
- init {
- TickEvent.subscribe("MC:push") {
- while (true) {
- inGameHud.chatHud.addMessage(messageQueue.poll() ?: break)
- }
- while (true) {
- (nextTickTodos.poll() ?: break).invoke()
- }
- }
- }
+ init {
+ TickEvent.subscribe("MC:push") {
+ while (true) {
+ inGameHud.chatHud.addMessage(messageQueue.poll() ?: break)
+ }
+ while (true) {
+ (nextTickTodos.poll() ?: break).invoke()
+ }
+ }
+ }
- fun sendChat(text: Text) {
- if (instance.isOnThread)
- inGameHud.chatHud.addMessage(text)
- else
- messageQueue.add(text)
- }
+ fun sendChat(text: Text) {
+ if (instance.isOnThread)
+ inGameHud.chatHud.addMessage(text)
+ else
+ messageQueue.add(text)
+ }
- fun sendServerCommand(command: String) {
- val nh = player?.networkHandler ?: return
- nh.sendPacket(
- CommandExecutionC2SPacket(
- command,
- )
- )
- }
+ fun sendServerCommand(command: String) {
+ val nh = player?.networkHandler ?: return
+ nh.sendPacket(
+ CommandExecutionC2SPacket(
+ command,
+ )
+ )
+ }
- fun sendServerChat(text: String) {
- player?.networkHandler?.sendChatMessage(text)
- }
+ fun sendServerChat(text: String) {
+ player?.networkHandler?.sendChatMessage(text)
+ }
- fun sendCommand(command: String) {
- player?.networkHandler?.sendCommand(command)
- }
+ fun sendCommand(command: String) {
+ player?.networkHandler?.sendCommand(command)
+ }
- fun onMainThread(block: () -> Unit) {
- if (instance.isOnThread)
- block()
- else
- instance.send(block)
- }
+ fun onMainThread(block: () -> Unit) {
+ if (instance.isOnThread)
+ block()
+ else
+ instance.send(block)
+ }
- private val nextTickTodos = ConcurrentLinkedQueue<() -> Unit>()
- fun nextTick(function: () -> Unit) {
- nextTickTodos.add(function)
- }
+ private val nextTickTodos = ConcurrentLinkedQueue<() -> Unit>()
+ fun nextTick(function: () -> Unit) {
+ nextTickTodos.add(function)
+ }
- inline val resourceManager get() = (instance.resourceManager as ReloadableResourceManagerImpl)
- inline val worldRenderer: WorldRenderer get() = instance.worldRenderer
- inline val networkHandler get() = player?.networkHandler
- inline val instance get() = MinecraftClient.getInstance()
- inline val keyboard get() = instance.keyboard
- inline val textureManager get() = instance.textureManager
- inline val inGameHud get() = instance.inGameHud
- inline val font get() = instance.textRenderer
- inline val soundManager get() = instance.soundManager
- inline val player get() = instance.player
- inline val camera get() = instance.cameraEntity
- inline val guiAtlasManager get() = instance.guiAtlasManager
- inline val world get() = instance.world
- inline var screen
- get() = instance.currentScreen
- set(value) = instance.setScreen(value)
- inline val handledScreen: HandledScreen<*>? get() = instance.currentScreen as? HandledScreen<*>
- inline val window get() = instance.window
- inline val currentRegistries: RegistryWrapper.WrapperLookup? get() = world?.registryManager
- val defaultRegistries: RegistryWrapper.WrapperLookup = BuiltinRegistries.createWrapperLookup()
- val defaultItems = defaultRegistries.getWrapperOrThrow(RegistryKeys.ITEM)
+ inline val resourceManager get() = (instance.resourceManager as ReloadableResourceManagerImpl)
+ inline val worldRenderer: WorldRenderer get() = instance.worldRenderer
+ inline val networkHandler get() = player?.networkHandler
+ inline val instance get() = MinecraftClient.getInstance()
+ inline val keyboard get() = instance.keyboard
+ inline val interactionManager get() = instance.interactionManager
+ inline val textureManager get() = instance.textureManager
+ inline val options get() = instance.options
+ inline val inGameHud get() = instance.inGameHud
+ inline val font get() = instance.textRenderer
+ inline val soundManager get() = instance.soundManager
+ inline val player get() = instance.player
+ inline val camera get() = instance.cameraEntity
+ inline val guiAtlasManager get() = instance.guiAtlasManager
+ inline val world get() = instance.world
+ inline var screen
+ get() = instance.currentScreen
+ set(value) = instance.setScreen(value)
+ inline val handledScreen: HandledScreen<*>? get() = instance.currentScreen as? HandledScreen<*>
+ inline val window get() = instance.window
+ inline val currentRegistries: RegistryWrapper.WrapperLookup? get() = world?.registryManager
+ val defaultRegistries: RegistryWrapper.WrapperLookup = BuiltinRegistries.createWrapperLookup()
+ inline val currentOrDefaultRegistries get() = currentRegistries ?: defaultRegistries
+ val defaultItems: RegistryWrapper.Impl<Item> = defaultRegistries.getWrapperOrThrow(RegistryKeys.ITEM)
}
val Coordinate.blockPos: BlockPos
- get() = BlockPos(x, y, z)
+ get() = BlockPos(x, y, z)
diff --git a/src/main/kotlin/util/TimeMark.kt b/src/main/kotlin/util/TimeMark.kt
index 1264212..4a076ac 100644
--- a/src/main/kotlin/util/TimeMark.kt
+++ b/src/main/kotlin/util/TimeMark.kt
@@ -1,44 +1,52 @@
-
-
package moe.nea.firmament.util
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
class TimeMark private constructor(private val timeMark: Long) : Comparable<TimeMark> {
- fun passedTime() = if (timeMark == 0L) Duration.INFINITE else (System.currentTimeMillis() - timeMark).milliseconds
-
- operator fun minus(other: TimeMark): Duration {
- if (other.timeMark == timeMark)
- return 0.milliseconds
- if (other.timeMark == 0L)
- return Duration.INFINITE
- if (timeMark == 0L)
- return -Duration.INFINITE
- return (timeMark - other.timeMark).milliseconds
- }
-
- companion object {
- fun now() = TimeMark(System.currentTimeMillis())
- fun farPast() = TimeMark(0L)
- fun ago(timeDelta: Duration): TimeMark {
- if (timeDelta.isFinite()) {
- return TimeMark(System.currentTimeMillis() - timeDelta.inWholeMilliseconds)
- }
- require(timeDelta.isPositive())
- return farPast()
- }
- }
-
- override fun hashCode(): Int {
- return timeMark.hashCode()
- }
-
- override fun equals(other: Any?): Boolean {
- return other is TimeMark && other.timeMark == timeMark
- }
-
- override fun compareTo(other: TimeMark): Int {
- return this.timeMark.compareTo(other.timeMark)
- }
+ fun passedTime() =
+ if (timeMark == 0L) Duration.INFINITE
+ else (System.currentTimeMillis() - timeMark).milliseconds
+
+ fun passedAt(fakeNow: TimeMark) =
+ if (timeMark == 0L) Duration.INFINITE
+ else (fakeNow.timeMark - timeMark).milliseconds
+
+ operator fun minus(other: TimeMark): Duration {
+ if (other.timeMark == timeMark)
+ return 0.milliseconds
+ if (other.timeMark == 0L)
+ return Duration.INFINITE
+ if (timeMark == 0L)
+ return -Duration.INFINITE
+ return (timeMark - other.timeMark).milliseconds
+ }
+
+ companion object {
+ fun now() = TimeMark(System.currentTimeMillis())
+ fun farPast() = TimeMark(0L)
+ fun ago(timeDelta: Duration): TimeMark {
+ if (timeDelta.isFinite()) {
+ return TimeMark(System.currentTimeMillis() - timeDelta.inWholeMilliseconds)
+ }
+ require(timeDelta.isPositive())
+ return farPast()
+ }
+ }
+
+ override fun hashCode(): Int {
+ return timeMark.hashCode()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is TimeMark && other.timeMark == timeMark
+ }
+
+ override fun toString(): String {
+ return "https://time.is/$timeMark"
+ }
+
+ override fun compareTo(other: TimeMark): Int {
+ return this.timeMark.compareTo(other.timeMark)
+ }
}
diff --git a/src/main/kotlin/util/mc/SNbtFormatter.kt b/src/main/kotlin/util/mc/SNbtFormatter.kt
new file mode 100644
index 0000000..e773927
--- /dev/null
+++ b/src/main/kotlin/util/mc/SNbtFormatter.kt
@@ -0,0 +1,138 @@
+package moe.nea.firmament.util.mc
+
+import net.minecraft.nbt.NbtByte
+import net.minecraft.nbt.NbtByteArray
+import net.minecraft.nbt.NbtCompound
+import net.minecraft.nbt.NbtDouble
+import net.minecraft.nbt.NbtElement
+import net.minecraft.nbt.NbtEnd
+import net.minecraft.nbt.NbtFloat
+import net.minecraft.nbt.NbtInt
+import net.minecraft.nbt.NbtIntArray
+import net.minecraft.nbt.NbtList
+import net.minecraft.nbt.NbtLong
+import net.minecraft.nbt.NbtLongArray
+import net.minecraft.nbt.NbtShort
+import net.minecraft.nbt.NbtString
+import net.minecraft.nbt.visitor.NbtElementVisitor
+
+class SNbtFormatter private constructor() : NbtElementVisitor {
+ private val result = StringBuilder()
+ private var indent = 0
+ private fun writeIndent() {
+ result.append("\t".repeat(indent))
+ }
+
+ private fun pushIndent() {
+ indent++
+ }
+
+ private fun popIndent() {
+ indent--
+ }
+
+ fun apply(element: NbtElement): StringBuilder {
+ element.accept(this)
+ return result
+ }
+
+
+ override fun visitString(element: NbtString) {
+ result.append(NbtString.escape(element.asString()))
+ }
+
+ override fun visitByte(element: NbtByte) {
+ result.append(element.numberValue()).append("b")
+ }
+
+ override fun visitShort(element: NbtShort) {
+ result.append(element.shortValue()).append("s")
+ }
+
+ override fun visitInt(element: NbtInt) {
+ result.append(element.intValue())
+ }
+
+ override fun visitLong(element: NbtLong) {
+ result.append(element.longValue()).append("L")
+ }
+
+ override fun visitFloat(element: NbtFloat) {
+ result.append(element.floatValue()).append("f")
+ }
+
+ override fun visitDouble(element: NbtDouble) {
+ result.append(element.doubleValue()).append("d")
+ }
+
+ private fun visitArrayContents(array: List<NbtElement>) {
+ array.forEachIndexed { index, element ->
+ writeIndent()
+ element.accept(this)
+ if (array.size != index + 1) {
+ result.append(",")
+ }
+ result.append("\n")
+ }
+ }
+
+ private fun writeArray(arrayTypeTag: String, array: List<NbtElement>) {
+ result.append("[").append(arrayTypeTag).append("\n")
+ pushIndent()
+ visitArrayContents(array)
+ popIndent()
+ writeIndent()
+ result.append("]")
+
+ }
+
+ override fun visitByteArray(element: NbtByteArray) {
+ writeArray("B;", element)
+ }
+
+ override fun visitIntArray(element: NbtIntArray) {
+ writeArray("I;", element)
+ }
+
+ override fun visitLongArray(element: NbtLongArray) {
+ writeArray("L;", element)
+ }
+
+ override fun visitList(element: NbtList) {
+ writeArray("", element)
+ }
+
+ override fun visitCompound(compound: NbtCompound) {
+ result.append("{\n")
+ pushIndent()
+ val keys = compound.keys.sorted()
+ keys.forEachIndexed { index, key ->
+ writeIndent()
+ val element = compound[key] ?: error("Key '$key' found but not present in compound: $compound")
+ val escapedName = if (key.matches(SIMPLE_NAME)) key else NbtString.escape(key)
+ result.append(escapedName).append(": ")
+ element.accept(this)
+ if (keys.size != index + 1) {
+ result.append(",")
+ }
+ result.append("\n")
+ }
+ popIndent()
+ writeIndent()
+ result.append("}")
+ }
+
+ override fun visitEnd(element: NbtEnd) {
+ result.append("END")
+ }
+
+ companion object {
+ fun prettify(nbt: NbtElement): String {
+ return SNbtFormatter().apply(nbt).toString()
+ }
+
+ fun NbtElement.toPrettyString() = prettify(this)
+
+ private val SIMPLE_NAME = "[A-Za-z0-9._+-]+".toRegex()
+ }
+}
diff --git a/src/main/kotlin/util/regex.kt b/src/main/kotlin/util/regex.kt
index 78c90e8..a44435c 100644
--- a/src/main/kotlin/util/regex.kt
+++ b/src/main/kotlin/util/regex.kt
@@ -1,8 +1,14 @@
+@file:OptIn(ExperimentalTypeInference::class, ExperimentalContracts::class)
+
package moe.nea.firmament.util
import java.util.regex.Matcher
import java.util.regex.Pattern
import org.intellij.lang.annotations.Language
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
+import kotlin.experimental.ExperimentalTypeInference
import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
@@ -10,10 +16,14 @@ import kotlin.time.Duration.Companion.seconds
inline fun <T> String.ifMatches(regex: Regex, block: (MatchResult) -> T): T? =
regex.matchEntire(this)?.let(block)
-inline fun <T> Pattern.useMatch(string: String, block: Matcher.() -> T): T? =
- matcher(string)
+inline fun <T> Pattern.useMatch(string: String, block: Matcher.() -> T): T? {
+ contract {
+ callsInPlace(block, InvocationKind.AT_MOST_ONCE)
+ }
+ return matcher(string)
.takeIf(Matcher::matches)
?.let(block)
+}
@Language("RegExp")
val TIME_PATTERN = "[0-9]+[ms]"
diff --git a/src/main/kotlin/util/skyblock/AbilityUtils.kt b/src/main/kotlin/util/skyblock/AbilityUtils.kt
new file mode 100644
index 0000000..0f0adbe
--- /dev/null
+++ b/src/main/kotlin/util/skyblock/AbilityUtils.kt
@@ -0,0 +1,138 @@
+package moe.nea.firmament.util.skyblock
+
+import kotlin.time.Duration
+import net.minecraft.item.ItemStack
+import net.minecraft.text.Text
+import moe.nea.firmament.util.ErrorUtil
+import moe.nea.firmament.util.directLiteralStringContent
+import moe.nea.firmament.util.mc.loreAccordingToNbt
+import moe.nea.firmament.util.parseShortNumber
+import moe.nea.firmament.util.parseTimePattern
+import moe.nea.firmament.util.unformattedString
+import moe.nea.firmament.util.useMatch
+
+object AbilityUtils {
+ data class ItemAbility(
+ val name: String,
+ val hasPowerScroll: Boolean,
+ val activation: AbilityActivation,
+ val manaCost: Int?,
+ val descriptionLines: List<Text>,
+ val cooldown: Duration?,
+ )
+
+ @JvmInline
+ value class AbilityActivation(
+ val label: String
+ ) {
+ companion object {
+ val RIGHT_CLICK = AbilityActivation("RIGHT CLICK")
+ val SNEAK_RIGHT_CLICK = AbilityActivation("SNEAK RIGHT CLICK")
+ val SNEAK = AbilityActivation("SNEAK")
+ val EMPTY = AbilityActivation("")
+ fun of(text: String?): AbilityActivation {
+ val trimmed = text?.trim()
+ if (trimmed.isNullOrBlank())
+ return EMPTY
+ return AbilityActivation(trimmed)
+ }
+ }
+ }
+
+ private val abilityNameRegex = "Ability: (?<name>.*?) *".toPattern()
+ private fun findAbility(iterator: ListIterator<Text>): ItemAbility? {
+ if (!iterator.hasNext()) {
+ return null
+ }
+ val line = iterator.next()
+ // The actual information about abilities is stored in the siblings
+ if (line.directLiteralStringContent != "") return null
+ var powerScroll: Boolean = false // This should instead determine the power scroll based on text colour
+ var abilityName: String? = null
+ var activation: String? = null
+ var hasProcessedActivation = false
+ for (sibling in line.siblings) {
+ val directContent = sibling.directLiteralStringContent ?: continue
+ if (directContent == "⦾ ") {
+ powerScroll = true
+ continue
+ }
+ if (!hasProcessedActivation && abilityName != null) {
+ hasProcessedActivation = true
+ activation = directContent
+ continue
+ }
+ abilityNameRegex.useMatch<Nothing>(directContent) {
+ abilityName = group("name")
+ continue
+ }
+ if (abilityName != null) {
+ ErrorUtil.softError("Found abilityName $abilityName without finding but encountered unprocessable element in: $line")
+ }
+ return null
+ }
+ if (abilityName == null) return null
+ val descriptionLines = mutableListOf<Text>()
+ var manaCost: Int? = null
+ var cooldown: Duration? = null
+ while (iterator.hasNext()) {
+ val descriptionLine = iterator.next()
+ if (descriptionLine.unformattedString == "") break
+ var nextIsManaCost = false
+ var isSpecialLine = false
+ var nextIsDuration = false
+ for (sibling in descriptionLine.siblings) {
+ val directContent = sibling.directLiteralStringContent ?: continue
+ if ("Mana Cost: " == directContent) { // TODO: 'Soulflow Cost: ' support (or maybe a generic 'XXX Cost: ')
+ nextIsManaCost = true
+ isSpecialLine = true
+ continue
+ }
+ if ("Cooldown: " == directContent) {
+ nextIsDuration = true
+ isSpecialLine = true
+ continue
+ }
+ if (nextIsDuration) {
+ nextIsDuration = false
+ cooldown = parseTimePattern(directContent)
+ continue
+ }
+ if (nextIsManaCost) {
+ nextIsManaCost = false
+ manaCost = parseShortNumber(directContent).toInt()
+ continue
+ }
+ if (isSpecialLine) {
+ ErrorUtil.softError("Unknown special line segment: '$sibling' in '$descriptionLine'")
+ }
+ }
+ if (!isSpecialLine) {
+ descriptionLines.add(descriptionLine)
+ }
+ }
+ return ItemAbility(
+ abilityName,
+ powerScroll,
+ AbilityActivation.of(activation),
+ manaCost,
+ descriptionLines,
+ cooldown
+ )
+ }
+
+ fun getAbilities(lore: List<Text>): List<ItemAbility> {
+ val iterator = lore.listIterator()
+ val abilities = mutableListOf<ItemAbility>()
+ while (iterator.hasNext()) {
+ findAbility(iterator)?.let(abilities::add)
+ }
+
+ return abilities
+ }
+
+ fun getAbilities(itemStack: ItemStack): List<ItemAbility> {
+ return getAbilities(itemStack.loreAccordingToNbt)
+ }
+
+}
diff --git a/src/main/kotlin/util/textutil.kt b/src/main/kotlin/util/textutil.kt
index 36924a6..1cef5d4 100644
--- a/src/main/kotlin/util/textutil.kt
+++ b/src/main/kotlin/util/textutil.kt
@@ -90,6 +90,8 @@ fun CharSequence.removeColorCodes(keepNonColorCodes: Boolean = false): String {
val Text.unformattedString: String
get() = string.removeColorCodes()
+val Text.directLiteralStringContent: String? get() = (this.content as? PlainTextContent)?.string()
+
fun Text.allSiblings(): List<Text> = listOf(this) + siblings.flatMap { it.allSiblings() }
fun MutableText.withColor(formatting: Formatting) = this.styled { it.withColor(formatting).withItalic(false) }