diff options
Diffstat (limited to 'src/main/kotlin/repo')
-rw-r--r-- | src/main/kotlin/repo/ExpensiveItemCacheApi.kt | 8 | ||||
-rw-r--r-- | src/main/kotlin/repo/HypixelStaticData.kt | 25 | ||||
-rw-r--r-- | src/main/kotlin/repo/ItemCache.kt | 113 | ||||
-rw-r--r-- | src/main/kotlin/repo/MiningRepoData.kt | 2 | ||||
-rw-r--r-- | src/main/kotlin/repo/ModernOverlaysData.kt | 41 | ||||
-rw-r--r-- | src/main/kotlin/repo/RepoManager.kt | 51 | ||||
-rw-r--r-- | src/main/kotlin/repo/SBItemStack.kt | 54 | ||||
-rw-r--r-- | src/main/kotlin/repo/recipes/SBEssenceUpgradeRecipeRenderer.kt | 2 |
8 files changed, 244 insertions, 52 deletions
diff --git a/src/main/kotlin/repo/ExpensiveItemCacheApi.kt b/src/main/kotlin/repo/ExpensiveItemCacheApi.kt new file mode 100644 index 0000000..eef95a6 --- /dev/null +++ b/src/main/kotlin/repo/ExpensiveItemCacheApi.kt @@ -0,0 +1,8 @@ +package moe.nea.firmament.repo + +/** + * Marker for functions that could potentially invoke DFU. Please do not call on a lot of objects at once, or try to make sure the item is cached and fall back to a more gentle function call using [SBItemStack.isWarm] and similar functions. + */ +@RequiresOptIn +@Retention(AnnotationRetention.BINARY) +annotation class ExpensiveItemCacheApi diff --git a/src/main/kotlin/repo/HypixelStaticData.kt b/src/main/kotlin/repo/HypixelStaticData.kt index 181aa70..b0ada77 100644 --- a/src/main/kotlin/repo/HypixelStaticData.kt +++ b/src/main/kotlin/repo/HypixelStaticData.kt @@ -3,21 +3,17 @@ package moe.nea.firmament.repo import io.ktor.client.call.body import io.ktor.client.request.get import org.apache.logging.log4j.LogManager -import org.lwjgl.glfw.GLFW import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlinx.coroutines.withTimeoutOrNull import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlin.time.Duration.Companion.minutes import moe.nea.firmament.Firmament import moe.nea.firmament.apis.CollectionResponse import moe.nea.firmament.apis.CollectionSkillData -import moe.nea.firmament.keybindings.IKeyBinding import moe.nea.firmament.util.SkyblockId -import moe.nea.firmament.util.async.waitForInput object HypixelStaticData { private val logger = LogManager.getLogger("Firmament.HypixelStaticData") @@ -25,7 +21,13 @@ object HypixelStaticData { private val hypixelApiBaseUrl = "https://api.hypixel.net" var lowestBin: Map<SkyblockId, Double> = mapOf() private set - var bazaarData: Map<SkyblockId, BazaarData> = mapOf() + var avg1dlowestBin: Map<SkyblockId, Double> = mapOf() + private set + var avg3dlowestBin: Map<SkyblockId, Double> = mapOf() + private set + var avg7dlowestBin: Map<SkyblockId, Double> = mapOf() + private set + var bazaarData: Map<SkyblockId.BazaarStock, BazaarData> = mapOf() private set var collectionData: Map<String, CollectionSkillData> = mapOf() private set @@ -56,9 +58,10 @@ object HypixelStaticData { val products: Map<SkyblockId.BazaarStock, BazaarData> = mapOf(), ) - fun getPriceOfItem(item: SkyblockId): Double? = bazaarData[item]?.quickStatus?.buyPrice ?: lowestBin[item] - fun hasBazaarStock(item: SkyblockId): Boolean { + fun getPriceOfItem(item: SkyblockId): Double? = bazaarData[SkyblockId.BazaarStock.fromSkyBlockId(item)]?.quickStatus?.buyPrice ?: lowestBin[item] + + fun hasBazaarStock(item: SkyblockId.BazaarStock): Boolean { return item in bazaarData } @@ -90,6 +93,12 @@ object HypixelStaticData { private suspend fun fetchPricesFromMoulberry() { lowestBin = Firmament.httpClient.get("$moulberryBaseUrl/lowestbin.json") .body<Map<SkyblockId, Double>>() + avg1dlowestBin = Firmament.httpClient.get("$moulberryBaseUrl/auction_averages_lbin/1day.json") + .body<Map<SkyblockId, Double>>() + avg3dlowestBin = Firmament.httpClient.get("$moulberryBaseUrl/auction_averages_lbin/3day.json") + .body<Map<SkyblockId, Double>>() + avg7dlowestBin = Firmament.httpClient.get("$moulberryBaseUrl/auction_averages_lbin/7day.json") + .body<Map<SkyblockId, Double>>() } private suspend fun fetchBazaarPrices() { @@ -97,7 +106,7 @@ object HypixelStaticData { if (!response.success) { logger.warn("Retrieved unsuccessful bazaar data") } - bazaarData = response.products.mapKeys { it.key.toRepoId() } + bazaarData = response.products } private suspend fun updateCollectionData() { diff --git a/src/main/kotlin/repo/ItemCache.kt b/src/main/kotlin/repo/ItemCache.kt index 09eedac..14decd8 100644 --- a/src/main/kotlin/repo/ItemCache.kt +++ b/src/main/kotlin/repo/ItemCache.kt @@ -8,8 +8,15 @@ import java.text.NumberFormat import java.util.UUID import java.util.concurrent.ConcurrentHashMap import org.apache.logging.log4j.LogManager -import kotlinx.coroutines.Job +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.cancel import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlin.io.path.readText import kotlin.jvm.optionals.getOrNull import net.minecraft.SharedConstants import net.minecraft.component.DataComponentTypes @@ -22,14 +29,18 @@ import net.minecraft.nbt.NbtCompound import net.minecraft.nbt.NbtElement import net.minecraft.nbt.NbtOps import net.minecraft.nbt.NbtString +import net.minecraft.nbt.StringNbtReader import net.minecraft.text.MutableText import net.minecraft.text.Style import net.minecraft.text.Text +import net.minecraft.util.Identifier import moe.nea.firmament.Firmament +import moe.nea.firmament.features.debug.ExportedTestConstantMeta import moe.nea.firmament.repo.RepoManager.initialize import moe.nea.firmament.util.LegacyFormattingCode import moe.nea.firmament.util.LegacyTagParser import moe.nea.firmament.util.MC +import moe.nea.firmament.util.MinecraftDispatcher import moe.nea.firmament.util.SkyblockId import moe.nea.firmament.util.TestUtil import moe.nea.firmament.util.directLiteralStringContent @@ -40,6 +51,7 @@ import moe.nea.firmament.util.mc.loreAccordingToNbt import moe.nea.firmament.util.mc.modifyLore import moe.nea.firmament.util.mc.setCustomName import moe.nea.firmament.util.mc.setSkullOwner +import moe.nea.firmament.util.skyblockId import moe.nea.firmament.util.transformEachRecursively object ItemCache : IReloadable { @@ -56,14 +68,18 @@ object ItemCache : IReloadable { putShort("Damage", damage.toShort()) } + @ExpensiveItemCacheApi private fun NbtCompound.transformFrom10809ToModern() = convert189ToModern(this@transformFrom10809ToModern) + val currentSaveVersion = SharedConstants.getGameVersion().saveVersion.id + + @ExpensiveItemCacheApi fun convert189ToModern(nbtComponent: NbtCompound): NbtCompound? = try { df.update( TypeReferences.ITEM_STACK, Dynamic(NbtOps.INSTANCE, nbtComponent), -1, - SharedConstants.getGameVersion().saveVersion.id + currentSaveVersion ).value as NbtCompound } catch (e: Exception) { isFlawless = false @@ -126,19 +142,48 @@ object ItemCache : IReloadable { return base } + fun tryFindFromModernFormat(skyblockId: SkyblockId): NbtCompound? { + val overlayFile = + RepoManager.overlayData.getMostModernReadableOverlay(skyblockId, currentSaveVersion) ?: return null + val overlay = StringNbtReader.readCompound(overlayFile.path.readText()) + val result = ExportedTestConstantMeta.SOURCE_CODEC.decode( + NbtOps.INSTANCE, overlay + ).result().getOrNull() ?: return null + val meta = result.first + return df.update( + TypeReferences.ITEM_STACK, + Dynamic(NbtOps.INSTANCE, result.second), + meta.dataVersion, + currentSaveVersion + ).value as NbtCompound + } + + @ExpensiveItemCacheApi private fun NEUItem.asItemStackNow(): ItemStack { + try { + var modernItemTag = tryFindFromModernFormat(this.skyblockId) val oldItemTag = get10809CompoundTag() - val modernItemTag = oldItemTag.transformFrom10809ToModern() - ?: return brokenItemStack(this) + var usedOldNbt = false + if (modernItemTag == null) { + usedOldNbt = true + modernItemTag = oldItemTag.transformFrom10809ToModern() + ?: return brokenItemStack(this) + } val itemInstance = ItemStack.fromNbt(MC.defaultRegistries, modernItemTag).getOrNull() ?: return brokenItemStack(this) + if (usedOldNbt) { + val tag = oldItemTag.getCompound("tag") + val extraAttributes = tag.flatMap { it.getCompound("ExtraAttributes") } + .getOrNull() + if (extraAttributes != null) + itemInstance.set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(extraAttributes)) + val itemModel = tag.flatMap { it.getString("ItemModel") }.getOrNull() + if (itemModel != null) + itemInstance.set(DataComponentTypes.ITEM_MODEL, Identifier.of(itemModel)) + } itemInstance.loreAccordingToNbt = lore.map { un189Lore(it) } itemInstance.displayNameAccordingToNbt = un189Lore(displayName) - val extraAttributes = oldItemTag.getCompound("tag").flatMap { it.getCompound("ExtraAttributes") } - .getOrNull() - if (extraAttributes != null) - itemInstance.set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(extraAttributes)) return itemInstance } catch (e: Exception) { e.printStackTrace() @@ -146,6 +191,11 @@ object ItemCache : IReloadable { } } + fun hasCacheFor(skyblockId: SkyblockId): Boolean { + return skyblockId.neuItem in cache + } + + @ExpensiveItemCacheApi fun NEUItem?.asItemStack(idHint: SkyblockId? = null, loreReplacements: Map<String, String>? = null): ItemStack { if (this == null) return brokenItemStack(null, idHint) var s = cache[this.skyblockItemId] @@ -179,22 +229,49 @@ object ItemCache : IReloadable { } } - var job: Job? = null + var itemRecacheScope: CoroutineScope? = null - override fun reload(repository: NEURepository) { - val j = job - if (j != null && j.isActive) { - j.cancel() + private var recacheSoonSubmitted = mutableSetOf<SkyblockId>() + + @OptIn(ExpensiveItemCacheApi::class) + fun recacheSoon(neuItem: NEUItem) { + itemRecacheScope?.launch { + if (!withContext(MinecraftDispatcher) { + recacheSoonSubmitted.add(neuItem.skyblockId) + }) { + return@launch + } + neuItem.asItemStack() } + } + + @OptIn(ExpensiveItemCacheApi::class) + override fun reload(repository: NEURepository) { + val j = itemRecacheScope + j?.cancel("New reload invoked") cache.clear() isFlawless = true if (TestUtil.isInTest) return - job = Firmament.coroutineScope.launch { - val items = repository.items?.items ?: return@launch - items.values.forEach { - it.asItemStack() // Rebuild cache - } + val newScope = + CoroutineScope( + Firmament.coroutineScope.coroutineContext + + SupervisorJob(Firmament.globalJob) + + Dispatchers.Default.limitedParallelism( + (Runtime.getRuntime().availableProcessors() / 4).coerceAtLeast(1) + ) + ) + val items = repository.items?.items + newScope.launch { + val items = items ?: return@launch + items.values.chunked(500).map { chunk -> + async { + chunk.forEach { + it.asItemStack() // Rebuild cache + } + } + }.awaitAll() } + itemRecacheScope = newScope } fun coinItem(coinAmount: Int): ItemStack { diff --git a/src/main/kotlin/repo/MiningRepoData.kt b/src/main/kotlin/repo/MiningRepoData.kt index e40292d..e96a241 100644 --- a/src/main/kotlin/repo/MiningRepoData.kt +++ b/src/main/kotlin/repo/MiningRepoData.kt @@ -81,6 +81,7 @@ class MiningRepoData : IReloadable { ) { @Transient val dropItem = baseDrop?.let(::SBItemStack) + @OptIn(ExpensiveItemCacheApi::class) private val labeledStack by lazy { dropItem?.asCopiedItemStack()?.also(::markItemStack) } @@ -110,6 +111,7 @@ class MiningRepoData : IReloadable { fun isActiveIn(location: SkyBlockIsland) = onlyIn == null || location in onlyIn + @OptIn(ExpensiveItemCacheApi::class) private fun convertToModernBlock(): Block? { // TODO: this should be in a shared util, really val newCompound = ItemCache.convert189ToModern(NbtCompound().apply { diff --git a/src/main/kotlin/repo/ModernOverlaysData.kt b/src/main/kotlin/repo/ModernOverlaysData.kt new file mode 100644 index 0000000..543b800 --- /dev/null +++ b/src/main/kotlin/repo/ModernOverlaysData.kt @@ -0,0 +1,41 @@ +package moe.nea.firmament.repo + +import io.github.moulberry.repo.IReloadable +import io.github.moulberry.repo.NEURepository +import java.nio.file.Path +import kotlin.io.path.extension +import kotlin.io.path.isDirectory +import kotlin.io.path.listDirectoryEntries +import kotlin.io.path.nameWithoutExtension +import moe.nea.firmament.util.SkyblockId + +// TODO: move this over to the repo parser +class ModernOverlaysData : IReloadable { + data class OverlayFile( + val version: Int, + val path: Path, + ) + + var overlays: Map<SkyblockId, List<OverlayFile>> = mapOf() + override fun reload(repo: NEURepository) { + val items = mutableMapOf<SkyblockId, MutableList<OverlayFile>>() + repo.baseFolder.resolve("itemsOverlay") + .takeIf { it.isDirectory() } + ?.listDirectoryEntries() + ?.forEach { versionFolder -> + val version = versionFolder.fileName.toString().toIntOrNull() ?: return@forEach + versionFolder.listDirectoryEntries() + .forEach { item -> + if (item.extension != "snbt") return@forEach + val itemId = item.nameWithoutExtension + items.getOrPut(SkyblockId(itemId)) { mutableListOf() }.add(OverlayFile(version, item)) + } + } + this.overlays = items + } + + fun getOverlayFiles(skyblockId: SkyblockId) = overlays[skyblockId] ?: listOf() + fun getMostModernReadableOverlay(skyblockId: SkyblockId, version: Int) = getOverlayFiles(skyblockId) + .filter { it.version <= version } + .maxByOrNull { it.version } +} diff --git a/src/main/kotlin/repo/RepoManager.kt b/src/main/kotlin/repo/RepoManager.kt index cc36fba..c3d1c52 100644 --- a/src/main/kotlin/repo/RepoManager.kt +++ b/src/main/kotlin/repo/RepoManager.kt @@ -7,10 +7,13 @@ import io.github.moulberry.repo.data.NEURecipe import io.github.moulberry.repo.data.Rarity import java.nio.file.Path import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import net.minecraft.client.MinecraftClient import net.minecraft.network.packet.s2c.play.SynchronizeRecipesS2CPacket import net.minecraft.recipe.display.CuttingRecipeDisplay +import net.minecraft.util.StringIdentifiable import moe.nea.firmament.Firmament import moe.nea.firmament.Firmament.logger import moe.nea.firmament.events.ReloadRegistrationEvent @@ -34,11 +37,13 @@ object RepoManager { branch = "master" save() } - + val enableREI by toggle("enable-rei") { true } val disableItemGroups by toggle("disable-item-groups") { true } val reload by button("reload") { save() - RepoManager.reload() + Firmament.coroutineScope.launch { + RepoManager.reload() + } } val redownload by button("redownload") { save() @@ -46,6 +51,19 @@ object RepoManager { } val alwaysSuperCraft by toggle("enable-super-craft") { true } var warnForMissingItemListMod by toggle("warn-for-missing-item-list-mod") { true } + val perfectRenders by choice("perfect-renders") { PerfectRender.RENDER } + } + + enum class PerfectRender(val label: String) : StringIdentifiable { + NOTHING("nothing"), + RENDER("render"), + RENDER_AND_TEXT("text"), + ; + + fun rendersPerfectText() = this == RENDER_AND_TEXT + fun rendersPerfectVisuals() = this == RENDER || this == RENDER_AND_TEXT + + override fun asString(): String? = label } val currentDownloadedSha by RepoDownloadManager::latestSavedVersionHash @@ -55,9 +73,11 @@ object RepoManager { val essenceRecipeProvider = EssenceRecipeProvider() val recipeCache = BetterRepoRecipeCache(essenceRecipeProvider, ReforgeStore) val miningData = MiningRepoData() + val overlayData = ModernOverlaysData() fun makeNEURepository(path: Path): NEURepository { return NEURepository.of(path).apply { + registerReloadListener(overlayData) registerReloadListener(ItemCache) registerReloadListener(RepoItemTypeCache) registerReloadListener(ExpLadders) @@ -118,16 +138,17 @@ object RepoManager { fun reloadForTest(from: Path) { neuRepo = makeNEURepository(from) - reload() + reloadSync() } - fun reload() { - if (!TestUtil.isInTest && !MC.instance.isOnThread) { - MC.instance.send { - reload() - } - return + + suspend fun reload() { + withContext(Dispatchers.IO) { + reloadSync() } + } + + fun reloadSync() { try { logger.info("Repo reload started.") neuRepo.reload() @@ -135,8 +156,10 @@ object RepoManager { } catch (exc: NEURepositoryException) { ErrorUtil.softError("Failed to reload repository", exc) MC.sendChat( - tr("firmament.repo.reloadfail", - "Failed to reload repository. This will result in some mod features not working.") + tr( + "firmament.repo.reloadfail", + "Failed to reload repository. This will result in some mod features not working." + ) ) } } @@ -153,7 +176,9 @@ object RepoManager { if (Config.autoUpdate) { launchAsyncUpdate() } else { - reload() + Firmament.coroutineScope.launch { + reload() + } } } @@ -181,4 +206,6 @@ object RepoManager { fun getRepoRef(): String { return "${Config.username}/${Config.reponame}#${Config.branch}" } + + fun shouldLoadREI(): Boolean = Config.enableREI } diff --git a/src/main/kotlin/repo/SBItemStack.kt b/src/main/kotlin/repo/SBItemStack.kt index 3690866..01d1c4d 100644 --- a/src/main/kotlin/repo/SBItemStack.kt +++ b/src/main/kotlin/repo/SBItemStack.kt @@ -225,14 +225,21 @@ data class SBItemStack constructor( Text.literal( buffKind.prefix + formattedAmount + statFormatting.postFix + - buffKind.postFix + " ") - .withColor(buffKind.color))) + buffKind.postFix + " " + ) + .withColor(buffKind.color) + ) + ) } fun formatValue() = - Text.literal(FirmFormatters.formatCommas(valueNum ?: 0.0, - 1, - includeSign = true) + statFormatting.postFix + " ") + Text.literal( + FirmFormatters.formatCommas( + valueNum ?: 0.0, + 1, + includeSign = true + ) + statFormatting.postFix + " " + ) .setStyle(Style.EMPTY.withColor(statFormatting.color)) val statFormatting = formattingOverrides[statName] ?: StatFormatting("", Formatting.GREEN) @@ -256,7 +263,7 @@ data class SBItemStack constructor( return segments.joinToString(" ") { it.replaceFirstChar { it.uppercaseChar() } } } - private fun parseStatLine(line: Text): StatLine? { + fun parseStatLine(line: Text): StatLine? { val sibs = line.siblings val stat = sibs.firstOrNull() ?: return null if (stat.style.color != TextColor.fromFormatting(Formatting.GRAY)) return null @@ -346,7 +353,9 @@ data class SBItemStack constructor( } // TODO: avoid instantiating the item stack here + @ExpensiveItemCacheApi val itemType: ItemType? get() = ItemType.fromItemStack(asImmutableItemStack()) + @ExpensiveItemCacheApi val rarity: Rarity? get() = Rarity.fromItem(asImmutableItemStack()) private var itemStack_: ItemStack? = null @@ -357,6 +366,7 @@ data class SBItemStack constructor( group("power").toInt() } ?: 0 + @ExpensiveItemCacheApi private val itemStack: ItemStack get() { val itemStack = itemStack_ ?: run { @@ -413,19 +423,35 @@ data class SBItemStack constructor( .append(starString(stars)) val isDungeon = ItemType.fromItemStack(itemStack)?.isDungeon ?: true val truncatedStarCount = if (isDungeon) minOf(5, stars) else stars - appendEnhancedStats(itemStack, - baseStats - .filter { it.statFormatting.isStarAffected } - .associate { - it.statName to ((it.valueNum ?: 0.0) * (truncatedStarCount * 0.02)) - }, - BuffKind.STAR_BUFF) + appendEnhancedStats( + itemStack, + baseStats + .filter { it.statFormatting.isStarAffected } + .associate { + it.statName to ((it.valueNum ?: 0.0) * (truncatedStarCount * 0.02)) + }, + BuffKind.STAR_BUFF + ) + } + + fun isWarm(): Boolean { + if (itemStack_ != null) return true + if (ItemCache.hasCacheFor(skyblockId)) return true + return false + } + + @OptIn(ExpensiveItemCacheApi::class) + fun asLazyImmutableItemStack(): ItemStack? { + if (isWarm()) return asImmutableItemStack() + return null } - fun asImmutableItemStack(): ItemStack { + @ExpensiveItemCacheApi + fun asImmutableItemStack(): ItemStack { // TODO: add a "or fallback to painting" option to asLazyImmutableItemStack to be used in more places. return itemStack } + @ExpensiveItemCacheApi fun asCopiedItemStack(): ItemStack { return itemStack.copy() } diff --git a/src/main/kotlin/repo/recipes/SBEssenceUpgradeRecipeRenderer.kt b/src/main/kotlin/repo/recipes/SBEssenceUpgradeRecipeRenderer.kt index 0f5271f..d358e6a 100644 --- a/src/main/kotlin/repo/recipes/SBEssenceUpgradeRecipeRenderer.kt +++ b/src/main/kotlin/repo/recipes/SBEssenceUpgradeRecipeRenderer.kt @@ -9,6 +9,7 @@ import net.minecraft.text.Text import net.minecraft.util.Identifier import moe.nea.firmament.Firmament import moe.nea.firmament.repo.EssenceRecipeProvider +import moe.nea.firmament.repo.ExpensiveItemCacheApi import moe.nea.firmament.repo.RepoManager import moe.nea.firmament.repo.SBItemStack import moe.nea.firmament.util.SkyblockId @@ -62,6 +63,7 @@ object SBEssenceUpgradeRecipeRenderer : GenericRecipeRenderer<EssenceRecipeProvi return listOfNotNull(SBItemStack(recipe.itemId)) } + @OptIn(ExpensiveItemCacheApi::class) override val icon: ItemStack get() = SBItemStack(SkyblockId("ESSENCE_WITHER")).asImmutableItemStack() override val title: Text = tr("firmament.category.essence", "Essence Upgrades") override val identifier: Identifier = Firmament.identifier("essence_upgrade") |