From 17a855a4bdfcca00b29f85981d8fb86968d9038c Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Tue, 11 Mar 2025 12:44:59 +0100 Subject: feat: add SBItemData implementation --- src/main/kotlin/commands/rome.kt | 50 ++++++++- src/main/kotlin/features/debug/PowerUserTools.kt | 2 +- src/main/kotlin/repo/item/SBBreakingPower.kt | 31 ++++++ src/main/kotlin/repo/item/SBItemData.kt | 122 ++++++++++++++++++++++ src/main/kotlin/repo/item/SBItemId.kt | 24 +++++ src/main/kotlin/repo/item/SBItemProperty.kt | 59 +++++++++++ src/main/kotlin/repo/item/SBRarity.kt | 20 ++++ src/main/kotlin/repo/item/SBStackSize.kt | 24 +++++ src/main/kotlin/repo/item/SBStars.kt | 21 ++++ src/main/kotlin/util/MC.kt | 2 + src/main/kotlin/util/compatloader/CompatLoader.kt | 31 ++++-- src/main/kotlin/util/skyblock/Rarity.kt | 7 ++ 12 files changed, 383 insertions(+), 10 deletions(-) create mode 100644 src/main/kotlin/repo/item/SBBreakingPower.kt create mode 100644 src/main/kotlin/repo/item/SBItemData.kt create mode 100644 src/main/kotlin/repo/item/SBItemId.kt create mode 100644 src/main/kotlin/repo/item/SBItemProperty.kt create mode 100644 src/main/kotlin/repo/item/SBRarity.kt create mode 100644 src/main/kotlin/repo/item/SBStackSize.kt create mode 100644 src/main/kotlin/repo/item/SBStars.kt (limited to 'src/main/kotlin') diff --git a/src/main/kotlin/commands/rome.kt b/src/main/kotlin/commands/rome.kt index 8ae34f6..3b3ab7a 100644 --- a/src/main/kotlin/commands/rome.kt +++ b/src/main/kotlin/commands/rome.kt @@ -3,7 +3,9 @@ package moe.nea.firmament.commands import com.mojang.brigadier.CommandDispatcher import com.mojang.brigadier.arguments.StringArgumentType.string import io.ktor.client.statement.bodyAsText +import java.util.ServiceLoader import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource +import net.minecraft.item.ItemStack import net.minecraft.nbt.NbtOps import net.minecraft.text.Text import net.minecraft.text.TextCodecs @@ -25,6 +27,10 @@ import moe.nea.firmament.repo.HypixelStaticData import moe.nea.firmament.repo.ItemCache import moe.nea.firmament.repo.RepoDownloadManager import moe.nea.firmament.repo.RepoManager +import moe.nea.firmament.repo.item.SBItemId +import moe.nea.firmament.repo.item.SBItemData +import moe.nea.firmament.repo.item.SBItemProperty +import moe.nea.firmament.repo.item.SBStackSize import moe.nea.firmament.util.FirmFormatters import moe.nea.firmament.util.FirmFormatters.debugPath import moe.nea.firmament.util.FirmFormatters.formatBool @@ -33,9 +39,15 @@ import moe.nea.firmament.util.SBData import moe.nea.firmament.util.ScreenUtil import moe.nea.firmament.util.SkyblockId import moe.nea.firmament.util.accessors.messages +import moe.nea.firmament.util.blue import moe.nea.firmament.util.collections.InstanceList import moe.nea.firmament.util.collections.WeakCache +import moe.nea.firmament.util.gold +import moe.nea.firmament.util.grey +import moe.nea.firmament.util.lime import moe.nea.firmament.util.mc.SNbtFormatter +import moe.nea.firmament.util.mc.SNbtFormatter.Companion.toPrettyString +import moe.nea.firmament.util.red import moe.nea.firmament.util.tr import moe.nea.firmament.util.unformattedString @@ -223,6 +235,41 @@ fun firmamentCommand() = literal("firmament") { ScreenUtil.setScreenLater(MiningBlockInfoUi.makeScreen()) } } + thenLiteral("scratch") { + thenExecute { + val original = SBItemData.fromStack(MC.stackInHand) + original.debugStackCreation().forEach { + println("============== Applied modifier: ${it.lastAppliedModifier} (${it.data}) ==============") + if (it.stack.isEmpty) + println("") + else + println(ItemStack.CODEC + .encodeStart(MC.currentOrDefaultNbtOps, it.stack) + .orThrow.toPrettyString()) + } + println("============== FINISHED ==============") + val roundtripped = original.roundtrip() + fun printProp(prop: SBItemProperty) { + val data = roundtripped.getData(prop) + val oldData = original.getData(prop) + val dataT = Text.literal("${data}") + if (oldData == null) + dataT.gold() + else if (oldData == data) + dataT.lime() + else + dataT.red() + source.sendFeedback(Text.literal("${prop.javaClass.simpleName}") + .blue() + .append(Text.literal(": ").grey()) + .append(dataT)) + } + SBItemProperty.allProperties.forEach { prop -> + printProp(prop) + } + source.sendFeedback(tr("firmament.itemdebug.done", "Item reconstruction finished, check your console.")) + } + } thenLiteral("dumpchat") { thenExecute { MC.inGameHud.chatHud.messages.forEach { @@ -252,7 +299,8 @@ fun firmamentCommand() = literal("firmament") { source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.gametype", locrawInfo.gametype)) source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.mode", locrawInfo.mode)) source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.map", locrawInfo.map)) - source.sendFeedback(tr("firmament.sbinfo.custommining", "Custom Mining: ${formatBool(locrawInfo.skyblockLocation?.hasCustomMining ?: false)}")) + source.sendFeedback(tr("firmament.sbinfo.custommining", + "Custom Mining: ${formatBool(locrawInfo.skyblockLocation?.hasCustomMining ?: false)}")) } } } diff --git a/src/main/kotlin/features/debug/PowerUserTools.kt b/src/main/kotlin/features/debug/PowerUserTools.kt index 8be5d5d..a2d9b9e 100644 --- a/src/main/kotlin/features/debug/PowerUserTools.kt +++ b/src/main/kotlin/features/debug/PowerUserTools.kt @@ -181,7 +181,7 @@ object PowerUserTools : FirmamentFeature { } else if (it.matches(TConfig.copyItemStack)) { ClipboardUtils.setTextContent( ItemStack.CODEC - .encodeStart(MC.currentOrDefaultRegistries.getOps(NbtOps.INSTANCE), item) + .encodeStart(MC.currentOrDefaultNbtOps, item) .orThrow.toPrettyString()) lastCopiedStack = Pair(item, Text.stringifiedTranslatable("firmament.tooltip.copied.stack")) } diff --git a/src/main/kotlin/repo/item/SBBreakingPower.kt b/src/main/kotlin/repo/item/SBBreakingPower.kt new file mode 100644 index 0000000..4c88a48 --- /dev/null +++ b/src/main/kotlin/repo/item/SBBreakingPower.kt @@ -0,0 +1,31 @@ +package moe.nea.firmament.repo.item + +import com.google.auto.service.AutoService +import io.github.moulberry.repo.data.NEUItem +import net.minecraft.item.ItemStack +import moe.nea.firmament.util.mc.loreAccordingToNbt +import moe.nea.firmament.util.removeColorCodes +import moe.nea.firmament.util.unformattedString +import moe.nea.firmament.util.useMatch + +@AutoService(SBItemProperty::class) +object SBBreakingPower : SBItemProperty() { + private val BREAKING_POWER_REGEX = "Breaking Power (?[0-9]+)".toPattern() + + fun fromLore(string: String?): Int? { + return BREAKING_POWER_REGEX.useMatch(string) { + group("power").toInt() + } + } + + override fun fromNeuItem(neuItem: NEUItem, store: SBItemData): Int? { + return fromLore(neuItem.lore.firstOrNull()?.removeColorCodes()) + } + + override fun fromStack( + stack: ItemStack, + store: SBItemData + ): Int? { + return fromLore(stack.loreAccordingToNbt.firstOrNull()?.unformattedString) + } +} diff --git a/src/main/kotlin/repo/item/SBItemData.kt b/src/main/kotlin/repo/item/SBItemData.kt new file mode 100644 index 0000000..7a6b081 --- /dev/null +++ b/src/main/kotlin/repo/item/SBItemData.kt @@ -0,0 +1,122 @@ +package moe.nea.firmament.repo.item + +import kotlin.collections.runningFold +import net.minecraft.item.ItemStack +import moe.nea.firmament.repo.RepoManager + +class SBItemData { + private var itemCache: ItemStack? = null + + private val data: MutableMap, Any> = mutableMapOf() + + fun getData(prop: SBItemProperty): T? { + return data[prop] as T? + } + + fun , T> set(property: Prop, data: T) { + if (data != null) { + (this.data as MutableMap)[property] = data + } else { + this.data.remove(property) + } + itemCache = null + } + + private fun enhanceStack(stack: ItemStack, property: SBItemProperty.State): ItemStack { + val data = getData(property) + return property.applyToStack(stack, this, data) + } + + private fun createStack(): ItemStack { + return SBItemProperty.allStates.fold(ItemStack.EMPTY) { stack, prop -> + enhanceStack(stack, prop) + } + } + + fun debugStackCreation(): List> { + fun combinedEnhanceStack(previous: PartialStack<*>, mod: SBItemProperty.State): PartialStack { + val nextStack = enhanceStack(previous.stack.copy(), mod) + return PartialStack(mod, getData(mod), nextStack) + } + return SBItemProperty.allStates + .runningFold( + PartialStack(null, null, ItemStack.EMPTY)) { stack: PartialStack<*>, prop -> + combinedEnhanceStack(stack, prop) + } + } + + /** + * Creates an [ItemStack] based on the current properties. The returned item stack must not be modified by the + * caller. + */ + fun toImmutableStack(): ItemStack { + var cached = itemCache + if (cached == null) { + cached = createStack() + itemCache = cached + } + return cached + } + + data class PartialStack( + val lastAppliedModifier: SBItemProperty?, + val data: T?, + val stack: ItemStack, + ) + + companion object { + /** + * Create an [SBItemData] from only the given characteristica. Any unspecified characteristica will be non-existent. + * If you want to compute all other properties based on the given properties, use [roundtrip]. + */ + fun fromCharacteristica( + vararg char: SBItemProperty.BoundState<*> + ): SBItemData { + val store = SBItemData() + char.forEach { + it.applyTo(store) + } + return store + } + + fun fromStack(itemStack: ItemStack): SBItemData { + val store = SBItemData() + store.loadFrom(itemStack) + return store + } + } + + /** + * Creates a new [SBItemData] from the item stack this [SBItemData] produces. This will initialize all properties. + */ + fun roundtrip(): SBItemData { + return fromStack(toImmutableStack()) + } + + /** + * Creates a new [SBItemData] with cheap inferences completed only by using data available in [io.github.moulberry.repo.data.NEUItem]. This is a cheaper version of [roundtrip], that does not create any [ItemStack]s, and preserves all properties already provided. Check if the property you need overrides [SBItemProperty.fromNeuItem]. + */ + fun cheapInfer(): SBItemData { + val neuItem = getData(SBItemId)?.let { RepoManager.getNEUItem(it) } ?: return this + val store = SBItemData() + SBItemProperty.allProperties.forEach { + it.fromNeuItem(neuItem, this) + } + store.data.putAll(this.data) + return store + } + + private fun loadFrom(stack: ItemStack) { + SBItemProperty.allProperties.forEach { + loadModifier(stack, it) + } + } + + private fun loadModifier( + stack: ItemStack, + modifier: SBItemProperty + ) { + val data = modifier.fromStack(stack, this) ?: return + set(modifier, data) + } +} diff --git a/src/main/kotlin/repo/item/SBItemId.kt b/src/main/kotlin/repo/item/SBItemId.kt new file mode 100644 index 0000000..fcafff0 --- /dev/null +++ b/src/main/kotlin/repo/item/SBItemId.kt @@ -0,0 +1,24 @@ +package moe.nea.firmament.repo.item + +import com.google.auto.service.AutoService +import net.minecraft.item.ItemStack +import moe.nea.firmament.repo.ItemCache.asItemStack +import moe.nea.firmament.repo.RepoManager +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.skyBlockId + +@AutoService(SBItemProperty::class) +object SBItemId : SBItemProperty.State() { + + override fun fromStack(stack: ItemStack, store: SBItemData): SkyblockId? { + return stack.skyBlockId + } + + override fun applyToStack(stack: ItemStack, store: SBItemData, value: SkyblockId?): ItemStack { + val id = value ?: SkyblockId.NULL + return RepoManager.getNEUItem(id).asItemStack(idHint = id) + } + + override val order: Int + get() = -10000 +} diff --git a/src/main/kotlin/repo/item/SBItemProperty.kt b/src/main/kotlin/repo/item/SBItemProperty.kt new file mode 100644 index 0000000..55b8f01 --- /dev/null +++ b/src/main/kotlin/repo/item/SBItemProperty.kt @@ -0,0 +1,59 @@ +package moe.nea.firmament.repo.item + +import io.github.moulberry.repo.data.NEUItem +import net.minecraft.item.ItemStack +import moe.nea.firmament.repo.ItemCache.asItemStack +import moe.nea.firmament.util.compatloader.CompatLoader + +/** + * A property of a skyblock item. Not every skyblock item must have this property, but some should. + * + * Access to this class should be limited to [State.bindWith] and [SBItemData.getData]. + * @see State + */ +abstract class SBItemProperty { + data class BoundState(val property: State, val data: T) { + fun applyTo(store: SBItemData) { + store.set(property, data) + } + } + + // TODO: Actually implement and make use of this method. + /** + * Extract this property's state from a [NEUItem]. If this method returns something, it may be equivalent to [fromStack] with the neu item resolved to an item stack according to [asItemStack], but *should not* instantiate an item stack. This method may return null to indicate that it needs a fully constructed item stack to extract a property. This method return one value and then later return another value from [fromStack], but behaviour is generally discouraged. + */ + open fun fromNeuItem(neuItem: NEUItem, store: SBItemData): T? { + return null + } + + /** + * Extract this property's state from an [ItemStack]. This should be fully reversible (i.e. all info used to in [fromStack] needs to be set by [State.applyToStack]. + */ + abstract fun fromStack(stack: ItemStack, store: SBItemData): T? + + /** + * A property of a skyblock item that carriers state. Unlike a plain [SBItemProperty] these modifiers can be used + * to change the state of an item, including its rendering as a vanilla [ItemStack]. + */ + abstract class State : SBItemProperty() { + abstract fun applyToStack(stack: ItemStack, store: SBItemData, value: T?): ItemStack + fun bindWith(data: T) = BoundState(this, data) + } + + /** + * The order of this property relative to other properties. Lower values get computed first, so higher values may + * rely on their data being stored already (if that item stack has any of that data), and they can overwrite the + * rendering of the lower states. + */ + open val order: Int get() = 0 + + companion object { + val loader = CompatLoader>(SBItemProperty::class) + val allProperties by lazy { + loader.allValidInstances.sortedBy { it.order } + } + val allStates by lazy { + allProperties.filterIsInstance>() + } + } +} diff --git a/src/main/kotlin/repo/item/SBRarity.kt b/src/main/kotlin/repo/item/SBRarity.kt new file mode 100644 index 0000000..d2ddd0b --- /dev/null +++ b/src/main/kotlin/repo/item/SBRarity.kt @@ -0,0 +1,20 @@ +package moe.nea.firmament.repo.item + +import com.google.auto.service.AutoService +import io.github.moulberry.repo.data.NEUItem +import net.minecraft.item.ItemStack +import moe.nea.firmament.util.skyblock.Rarity + +@AutoService(SBItemProperty::class) +object SBRarity : SBItemProperty() { + override fun fromStack( + stack: ItemStack, + store: SBItemData + ): Rarity? { + return Rarity.fromItem(stack) + } + + override fun fromNeuItem(neuItem: NEUItem, store: SBItemData): Rarity? { + return Rarity.fromStringLore(neuItem.lore) + } +} diff --git a/src/main/kotlin/repo/item/SBStackSize.kt b/src/main/kotlin/repo/item/SBStackSize.kt new file mode 100644 index 0000000..a31facd --- /dev/null +++ b/src/main/kotlin/repo/item/SBStackSize.kt @@ -0,0 +1,24 @@ +package moe.nea.firmament.repo.item + +import com.google.auto.service.AutoService +import net.minecraft.item.ItemStack + +@AutoService(SBItemProperty::class) +object SBStackSize : SBItemProperty.State() { + override fun fromStack( + stack: ItemStack, + store: SBItemData + ): Int? { + return stack.count + } + + override fun applyToStack( + stack: ItemStack, + store: SBItemData, + value: Int? + ): ItemStack { + if (value != null) + stack.count = value + return stack + } +} diff --git a/src/main/kotlin/repo/item/SBStars.kt b/src/main/kotlin/repo/item/SBStars.kt new file mode 100644 index 0000000..1fa930f --- /dev/null +++ b/src/main/kotlin/repo/item/SBStars.kt @@ -0,0 +1,21 @@ +package moe.nea.firmament.repo.item + +import net.minecraft.item.ItemStack +import moe.nea.firmament.util.getUpgradeStars + +object SBStars : SBItemProperty.State() { + override fun applyToStack( + stack: ItemStack, + store: SBItemData, + value: Int? + ): ItemStack { + TODO() + } + + override fun fromStack( + stack: ItemStack, + store: SBItemData + ): Int? { + return stack.getUpgradeStars() + } +} diff --git a/src/main/kotlin/util/MC.kt b/src/main/kotlin/util/MC.kt index a0d2fc0..ef03887 100644 --- a/src/main/kotlin/util/MC.kt +++ b/src/main/kotlin/util/MC.kt @@ -14,6 +14,7 @@ import net.minecraft.client.world.ClientWorld import net.minecraft.entity.Entity import net.minecraft.item.Item import net.minecraft.item.ItemStack +import net.minecraft.nbt.NbtOps import net.minecraft.network.packet.c2s.play.CommandExecutionC2SPacket import net.minecraft.registry.BuiltinRegistries import net.minecraft.registry.RegistryKeys @@ -112,6 +113,7 @@ object MC { inline val currentRegistries: RegistryWrapper.WrapperLookup? get() = world?.registryManager val defaultRegistries: RegistryWrapper.WrapperLookup by lazy { BuiltinRegistries.createWrapperLookup() } inline val currentOrDefaultRegistries get() = currentRegistries ?: defaultRegistries + inline val currentOrDefaultNbtOps get() = currentOrDefaultRegistries.getOps(NbtOps.INSTANCE) val defaultItems: RegistryWrapper.Impl by lazy { defaultRegistries.getOrThrow(RegistryKeys.ITEM) } var lastWorld: World? = null get() { diff --git a/src/main/kotlin/util/compatloader/CompatLoader.kt b/src/main/kotlin/util/compatloader/CompatLoader.kt index 6b60e87..c3cc19c 100644 --- a/src/main/kotlin/util/compatloader/CompatLoader.kt +++ b/src/main/kotlin/util/compatloader/CompatLoader.kt @@ -5,29 +5,44 @@ import net.fabricmc.loader.api.FabricLoader import kotlin.reflect.KClass import kotlin.streams.asSequence import moe.nea.firmament.Firmament +import moe.nea.firmament.util.ErrorUtil -abstract class CompatLoader(val kClass: Class) { +open class CompatLoader(val kClass: Class) { constructor(kClass: KClass) : this(kClass.java) val loader: ServiceLoader = ServiceLoader.load(kClass) val allValidInstances by lazy { - loader.reload() - loader.stream() + val resources = kClass.classLoader.getResources("META-INF/services/${kClass.name}") + val classes = resources .asSequence() - .filter { provider -> + .map { ErrorUtil.catch("Could not read service loader resource at $it") { it.readText() }.or { "" } } + .flatMap { it.lineSequence() } + .map { it.substringBefore('#').trim() } + .filter { it.isNotBlank() } + .mapNotNull { + ErrorUtil.catch("Could not load class named $it for $kClass") { + Class.forName(it, + false, + kClass.classLoader).asSubclass(kClass) + }.or { null } + } + .toList() + + classes.asSequence() + .filter { clazz -> runCatching { - shouldLoad(provider.type()) + shouldLoad(clazz) }.getOrElse { Firmament.logger.error("Could not determine whether to load a ${kClass.name} subclass", it) false } } - .mapNotNull { provider -> + .mapNotNull { clazz -> runCatching { - provider.get() + clazz.kotlin.objectInstance ?: clazz.getConstructor().newInstance() }.getOrElse { Firmament.logger.error( - "Could not load desired instance ${provider.type().name} for ${kClass.name}", + "Could not load desired instance ${clazz.name} for ${kClass.name}", it) null } diff --git a/src/main/kotlin/util/skyblock/Rarity.kt b/src/main/kotlin/util/skyblock/Rarity.kt index b19f371..f244ef6 100644 --- a/src/main/kotlin/util/skyblock/Rarity.kt +++ b/src/main/kotlin/util/skyblock/Rarity.kt @@ -15,6 +15,7 @@ import moe.nea.firmament.util.StringUtil.words import moe.nea.firmament.util.collections.lastNotNullOfOrNull import moe.nea.firmament.util.mc.loreAccordingToNbt import moe.nea.firmament.util.petData +import moe.nea.firmament.util.removeColorCodes import moe.nea.firmament.util.unformattedString typealias RepoRarity = io.github.moulberry.repo.data.Rarity @@ -87,6 +88,12 @@ enum class Rarity(vararg altNames: String) { fun fromPetItem(itemStack: ItemStack): Rarity? = itemStack.petData?.tier?.let(::fromNeuRepo) + fun fromStringLore(lore: List): Rarity? { + return lore.lastNotNullOfOrNull { + it.removeColorCodes().words().firstNotNullOfOrNull(::fromString) + } + } + fun fromLore(lore: List): Rarity? = lore.lastNotNullOfOrNull { it.unformattedString.words() -- cgit