Make pickaxe ability display use AbilityUtils

[no changelog]
This commit is contained in:
Linnea Gräf
2024-10-13 17:32:10 +02:00
parent daa63bd914
commit e6142bb936
24 changed files with 1433 additions and 681 deletions

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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
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
}
fun passedAt(fakeNow: TimeMark) =
if (timeMark == 0L) Duration.INFINITE
else (fakeNow.timeMark - 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()
}
}
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
}
override fun hashCode(): Int {
return timeMark.hashCode()
}
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 equals(other: Any?): Boolean {
return other is TimeMark && other.timeMark == timeMark
}
override fun hashCode(): Int {
return timeMark.hashCode()
}
override fun compareTo(other: TimeMark): Int {
return this.timeMark.compareTo(other.timeMark)
}
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)
}
}

View File

@@ -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()
}
}

View File

@@ -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]"

View File

@@ -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)
}
}

View File

@@ -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) }