diff options
Diffstat (limited to 'src/main/kotlin/util')
-rw-r--r-- | src/main/kotlin/util/ErrorUtil.kt | 16 | ||||
-rw-r--r-- | src/main/kotlin/util/MC.kt | 131 | ||||
-rw-r--r-- | src/main/kotlin/util/TimeMark.kt | 82 | ||||
-rw-r--r-- | src/main/kotlin/util/mc/SNbtFormatter.kt | 138 | ||||
-rw-r--r-- | src/main/kotlin/util/regex.kt | 14 | ||||
-rw-r--r-- | src/main/kotlin/util/skyblock/AbilityUtils.kt | 138 | ||||
-rw-r--r-- | src/main/kotlin/util/textutil.kt | 2 |
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) } |