From e16c60169bf192b79991176b5f9cee66b5b16e7d Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Tue, 24 Dec 2024 03:58:43 +0100 Subject: WIP: Reforge recipes --- src/main/kotlin/repo/BetterRepoRecipeCache.kt | 36 +++---- src/main/kotlin/repo/EssenceRecipeProvider.kt | 75 ++++++------- src/main/kotlin/repo/ExtraRecipeProvider.kt | 7 ++ src/main/kotlin/repo/ItemCache.kt | 47 ++++++++- src/main/kotlin/repo/Reforge.kt | 146 ++++++++++++++++++++++++++ src/main/kotlin/repo/ReforgeStore.kt | 124 ++++++++++++++++++++++ src/main/kotlin/repo/RepoItemTypeCache.kt | 15 +++ src/main/kotlin/repo/RepoManager.kt | 6 +- src/main/kotlin/repo/SBItemStack.kt | 21 +++- 9 files changed, 412 insertions(+), 65 deletions(-) create mode 100644 src/main/kotlin/repo/ExtraRecipeProvider.kt create mode 100644 src/main/kotlin/repo/Reforge.kt create mode 100644 src/main/kotlin/repo/ReforgeStore.kt create mode 100644 src/main/kotlin/repo/RepoItemTypeCache.kt (limited to 'src/main/kotlin/repo') diff --git a/src/main/kotlin/repo/BetterRepoRecipeCache.kt b/src/main/kotlin/repo/BetterRepoRecipeCache.kt index 91a6b50..4b32e57 100644 --- a/src/main/kotlin/repo/BetterRepoRecipeCache.kt +++ b/src/main/kotlin/repo/BetterRepoRecipeCache.kt @@ -1,4 +1,3 @@ - package moe.nea.firmament.repo import io.github.moulberry.repo.IReloadable @@ -6,23 +5,22 @@ import io.github.moulberry.repo.NEURepository import io.github.moulberry.repo.data.NEURecipe import moe.nea.firmament.util.SkyblockId -class BetterRepoRecipeCache(val essenceRecipeProvider: EssenceRecipeProvider) : IReloadable { - var usages: Map> = mapOf() - var recipes: Map> = mapOf() +class BetterRepoRecipeCache(vararg val extraProviders: ExtraRecipeProvider) : IReloadable { + var usages: Map> = mapOf() + var recipes: Map> = mapOf() - override fun reload(repository: NEURepository) { - val usages = mutableMapOf>() - val recipes = mutableMapOf>() - val baseRecipes = repository.items.items.values - .asSequence() - .flatMap { it.recipes } - val extraRecipes = essenceRecipeProvider.recipes - (baseRecipes + extraRecipes) - .forEach { recipe -> - recipe.allInputs.forEach { usages.getOrPut(SkyblockId(it.itemId), ::mutableSetOf).add(recipe) } - recipe.allOutputs.forEach { recipes.getOrPut(SkyblockId(it.itemId), ::mutableSetOf).add(recipe) } - } - this.usages = usages - this.recipes = recipes - } + override fun reload(repository: NEURepository) { + val usages = mutableMapOf>() + val recipes = mutableMapOf>() + val baseRecipes = repository.items.items.values + .asSequence() + .flatMap { it.recipes } + (baseRecipes + extraProviders.flatMap { it.provideExtraRecipes() }) + .forEach { recipe -> + recipe.allInputs.forEach { usages.getOrPut(SkyblockId(it.itemId), ::mutableSetOf).add(recipe) } + recipe.allOutputs.forEach { recipes.getOrPut(SkyblockId(it.itemId), ::mutableSetOf).add(recipe) } + } + this.usages = usages + this.recipes = recipes + } } diff --git a/src/main/kotlin/repo/EssenceRecipeProvider.kt b/src/main/kotlin/repo/EssenceRecipeProvider.kt index 1833258..38559d5 100644 --- a/src/main/kotlin/repo/EssenceRecipeProvider.kt +++ b/src/main/kotlin/repo/EssenceRecipeProvider.kt @@ -1,4 +1,3 @@ - package moe.nea.firmament.repo import io.github.moulberry.repo.IReloadable @@ -7,44 +6,46 @@ import io.github.moulberry.repo.data.NEUIngredient import io.github.moulberry.repo.data.NEURecipe import moe.nea.firmament.util.SkyblockId -class EssenceRecipeProvider : IReloadable { - data class EssenceUpgradeRecipe( - val itemId: SkyblockId, - val starCountAfter: Int, - val essenceCost: Int, - val essenceType: String, // TODO: replace with proper type - val extraItems: List, - ) : NEURecipe { - val essenceIngredient= NEUIngredient.fromString("${essenceType}:$essenceCost") - val allUpgradeComponents = listOf(essenceIngredient) + extraItems +class EssenceRecipeProvider : IReloadable, ExtraRecipeProvider { + data class EssenceUpgradeRecipe( + val itemId: SkyblockId, + val starCountAfter: Int, + val essenceCost: Int, + val essenceType: String, // TODO: replace with proper type + val extraItems: List, + ) : NEURecipe { + val essenceIngredient = NEUIngredient.fromString("${essenceType}:$essenceCost") + val allUpgradeComponents = listOf(essenceIngredient) + extraItems + + override fun getAllInputs(): Collection { + return listOf(NEUIngredient.fromString(itemId.neuItem + ":1")) + allUpgradeComponents + } - override fun getAllInputs(): Collection { - return listOf(NEUIngredient.fromString(itemId.neuItem + ":1")) + allUpgradeComponents - } + override fun getAllOutputs(): Collection { + return listOf(NEUIngredient.fromString(itemId.neuItem + ":1")) + } + } - override fun getAllOutputs(): Collection { - return listOf(NEUIngredient.fromString(itemId.neuItem + ":1")) - } - } + var recipes = listOf() + private set - var recipes = listOf() - private set + override fun provideExtraRecipes(): Iterable = recipes - override fun reload(repository: NEURepository) { - val recipes = mutableListOf() - for ((neuId, costs) in repository.constants.essenceCost.costs) { - // TODO: add dungeonization costs. this is in repo, but not in the repo parser. - for ((starCountAfter, essenceCost) in costs.essenceCosts.entries) { - val items = costs.itemCosts[starCountAfter] ?: emptyList() - recipes.add( - EssenceUpgradeRecipe( - SkyblockId(neuId), - starCountAfter, - essenceCost, - "ESSENCE_" + costs.type.uppercase(), // how flimsy - items.map { NEUIngredient.fromString(it) })) - } - } - this.recipes = recipes - } + override fun reload(repository: NEURepository) { + val recipes = mutableListOf() + for ((neuId, costs) in repository.constants.essenceCost.costs) { + // TODO: add dungeonization costs. this is in repo, but not in the repo parser. + for ((starCountAfter, essenceCost) in costs.essenceCosts.entries) { + val items = costs.itemCosts[starCountAfter] ?: emptyList() + recipes.add( + EssenceUpgradeRecipe( + SkyblockId(neuId), + starCountAfter, + essenceCost, + "ESSENCE_" + costs.type.uppercase(), // how flimsy + items.map { NEUIngredient.fromString(it) })) + } + } + this.recipes = recipes + } } diff --git a/src/main/kotlin/repo/ExtraRecipeProvider.kt b/src/main/kotlin/repo/ExtraRecipeProvider.kt new file mode 100644 index 0000000..9d3b5a0 --- /dev/null +++ b/src/main/kotlin/repo/ExtraRecipeProvider.kt @@ -0,0 +1,7 @@ +package moe.nea.firmament.repo + +import io.github.moulberry.repo.data.NEURecipe + +interface ExtraRecipeProvider { + fun provideExtraRecipes(): Iterable +} diff --git a/src/main/kotlin/repo/ItemCache.kt b/src/main/kotlin/repo/ItemCache.kt index 014de7d..f88dd48 100644 --- a/src/main/kotlin/repo/ItemCache.kt +++ b/src/main/kotlin/repo/ItemCache.kt @@ -24,21 +24,26 @@ import net.minecraft.nbt.NbtCompound import net.minecraft.nbt.NbtElement import net.minecraft.nbt.NbtOps import net.minecraft.nbt.NbtString +import net.minecraft.text.Style import net.minecraft.text.Text import moe.nea.firmament.Firmament import moe.nea.firmament.gui.config.HudMeta import moe.nea.firmament.gui.config.HudPosition import moe.nea.firmament.gui.hud.MoulConfigHud 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.SkyblockId import moe.nea.firmament.util.TestUtil +import moe.nea.firmament.util.directLiteralStringContent import moe.nea.firmament.util.mc.FirmamentDataComponentTypes import moe.nea.firmament.util.mc.appendLore +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.transformEachRecursively object ItemCache : IReloadable { private val cache: MutableMap = ConcurrentHashMap() @@ -70,6 +75,7 @@ object ItemCache : IReloadable { val ItemStack.isBroken get() = get(FirmamentDataComponentTypes.IS_BROKEN) ?: false + fun brokenItemStack(neuItem: NEUItem?, idHint: SkyblockId? = null): ItemStack { return ItemStack(Items.PAINTING).apply { setCustomName(Text.literal(neuItem?.displayName ?: idHint?.neuItem ?: "null")) @@ -88,6 +94,35 @@ object ItemCache : IReloadable { } } + fun un189Lore(lore: String): Text { + val base = Text.literal("") + base.setStyle(Style.EMPTY.withItalic(false)) + var lastColorCode = Style.EMPTY + var readOffset = 0 + while (readOffset < lore.length) { + var nextCode = lore.indexOf('§', readOffset) + if (nextCode < 0) { + nextCode = lore.length + } + val text = lore.substring(readOffset, nextCode) + if (text.isNotEmpty()) { + base.append(Text.literal(text).setStyle(lastColorCode)) + } + readOffset = nextCode + 2 + if (nextCode + 1 < lore.length) { + val colorCode = lore[nextCode + 1] + val formatting = LegacyFormattingCode.byCode[colorCode.lowercaseChar()] ?: LegacyFormattingCode.RESET + val modernFormatting = formatting.modern + if (modernFormatting.isColor) { + lastColorCode = Style.EMPTY.withColor(modernFormatting) + } else { + lastColorCode = lastColorCode.withFormatting(modernFormatting) + } + } + } + return base + } + private fun NEUItem.asItemStackNow(): ItemStack { try { val oldItemTag = get10809CompoundTag() @@ -95,6 +130,7 @@ object ItemCache : IReloadable { ?: return brokenItemStack(this) val itemInstance = ItemStack.fromNbt(MC.defaultRegistries, modernItemTag).getOrNull() ?: return brokenItemStack(this) + itemInstance.loreAccordingToNbt = lore.map { un189Lore(it) } val extraAttributes = oldItemTag.getCompound("tag").getCompound("ExtraAttributes") if (extraAttributes != null) itemInstance.set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(extraAttributes)) @@ -129,12 +165,13 @@ object ItemCache : IReloadable { } fun Text.applyLoreReplacements(loreReplacements: Map): Text { - assert(this.siblings.isEmpty()) - var string = this.string - loreReplacements.forEach { (find, replace) -> - string = string.replace("{$find}", replace) + return this.transformEachRecursively { + var string = it.directLiteralStringContent ?: return@transformEachRecursively it + loreReplacements.forEach { (find, replace) -> + string = string.replace("{$find}", replace) + } + Text.literal(string).setStyle(it.style) } - return Text.literal(string).styled { this.style } } var job: Job? = null diff --git a/src/main/kotlin/repo/Reforge.kt b/src/main/kotlin/repo/Reforge.kt new file mode 100644 index 0000000..ea01818 --- /dev/null +++ b/src/main/kotlin/repo/Reforge.kt @@ -0,0 +1,146 @@ +package moe.nea.firmament.repo + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.serializer +import net.minecraft.item.Item +import net.minecraft.registry.RegistryKey +import net.minecraft.registry.RegistryKeys +import net.minecraft.util.Identifier +import moe.nea.firmament.util.ReforgeId +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.skyblock.ItemType +import moe.nea.firmament.util.skyblock.Rarity + +@Serializable +data class Reforge( + val reforgeName: String, + @SerialName("internalName") val reforgeStone: SkyblockId? = null, + val nbtModifier: ReforgeId? = null, + val requiredRarities: List? = null, + val itemTypes: @Serializable(with = ReforgeEligibilityFilter.ItemTypesSerializer::class) List? = null, + val allowOn: List? = null, + val reforgeCosts: RarityMapped? = null, + val reforgeAbility: RarityMapped? = null, + val reforgeStats: RarityMapped>? = null, +) { + val eligibleItems get() = allowOn ?: itemTypes ?: listOf() + + @Serializable(with = ReforgeEligibilityFilter.Serializer::class) + sealed interface ReforgeEligibilityFilter { + object ItemTypesSerializer : KSerializer> { + override val descriptor: SerialDescriptor + get() = JsonElement.serializer().descriptor + + override fun deserialize(decoder: Decoder): List { + decoder as JsonDecoder + val jsonElement = decoder.decodeJsonElement() + if (jsonElement is JsonPrimitive && jsonElement.isString) { + return jsonElement.content.split("/").map { AllowsItemType(ItemType.ofName(it)) } + } + if (jsonElement is JsonArray) { + return decoder.json.decodeFromJsonElement(serializer>(), jsonElement) + } + jsonElement as JsonObject + val filters = mutableListOf() + jsonElement["internalName"]?.let { + decoder.json.decodeFromJsonElement(serializer>(), it).forEach { + filters.add(AllowsInternalName(it)) + } + } + jsonElement["itemId"]?.let { + decoder.json.decodeFromJsonElement(serializer>(), it).forEach { + val ident = Identifier.tryParse(it) + if (ident != null) + filters.add(AllowsVanillaItemType(RegistryKey.of(RegistryKeys.ITEM, ident))) + } + } + return filters + } + + override fun serialize(encoder: Encoder, value: List) { + TODO("Not yet implemented") + } + } + + object Serializer : KSerializer { + override val descriptor: SerialDescriptor + get() = serializer().descriptor + + override fun deserialize(decoder: Decoder): ReforgeEligibilityFilter { + val jsonObject = serializer().deserialize(decoder) + jsonObject["internalName"]?.let { + return AllowsInternalName(SkyblockId((it as JsonPrimitive).content)) + } + jsonObject["itemType"]?.let { + return AllowsItemType(ItemType.ofName((it as JsonPrimitive).content)) + } + jsonObject["minecraftId"]?.let { + return AllowsVanillaItemType(RegistryKey.of(RegistryKeys.ITEM, + Identifier.of((it as JsonPrimitive).content))) + } + error("Unknown item type") + } + + override fun serialize(encoder: Encoder, value: ReforgeEligibilityFilter) { + TODO("Not yet implemented") + } + + } + + data class AllowsItemType(val itemType: ItemType) : ReforgeEligibilityFilter + data class AllowsInternalName(val internalName: SkyblockId) : ReforgeEligibilityFilter + data class AllowsVanillaItemType(val minecraftId: RegistryKey) : ReforgeEligibilityFilter + } + + + val reforgeId get() = nbtModifier ?: ReforgeId(reforgeName.lowercase()) + + @Serializable(with = RarityMapped.Serializer::class) + sealed interface RarityMapped { + class Serializer( + val values: KSerializer + ) : KSerializer> { + override val descriptor: SerialDescriptor + get() = JsonElement.serializer().descriptor + + val indirect = MapSerializer(Rarity.serializer(), values) + override fun deserialize(decoder: Decoder): RarityMapped { + decoder as JsonDecoder + val element = decoder.decodeJsonElement() + if (element is JsonObject) { + return PerRarity(decoder.json.decodeFromJsonElement(indirect, element)) + } else { + return Direct(decoder.json.decodeFromJsonElement(values, element)) + } + } + + override fun serialize(encoder: Encoder, value: RarityMapped) { + when (value) { + is Direct -> + values.serialize(encoder, value.value) + + is PerRarity -> + indirect.serialize(encoder, value.values) + } + } + } + + @Serializable + data class Direct(val value: T) : RarityMapped + + @Serializable + data class PerRarity(val values: Map) : RarityMapped + } + +} diff --git a/src/main/kotlin/repo/ReforgeStore.kt b/src/main/kotlin/repo/ReforgeStore.kt new file mode 100644 index 0000000..f03903b --- /dev/null +++ b/src/main/kotlin/repo/ReforgeStore.kt @@ -0,0 +1,124 @@ +package moe.nea.firmament.repo + +import com.google.gson.JsonElement +import com.mojang.serialization.JsonOps +import io.github.moulberry.repo.IReloadable +import io.github.moulberry.repo.NEURepoFile +import io.github.moulberry.repo.NEURepository +import io.github.moulberry.repo.NEURepositoryException +import io.github.moulberry.repo.data.NEURecipe +import kotlinx.serialization.KSerializer +import kotlinx.serialization.serializer +import net.minecraft.item.Item +import net.minecraft.registry.RegistryKey +import moe.nea.firmament.Firmament +import moe.nea.firmament.util.ReforgeId +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.json.KJsonOps +import moe.nea.firmament.util.skyblock.ItemType + +object ReforgeStore : ExtraRecipeProvider, IReloadable { + override fun provideExtraRecipes(): Iterable { + return emptyList() + } + + var byType: Map> = mapOf() + var byVanilla: Map, List> = mapOf() + var byInternalName: Map> = mapOf() + var modifierLut = mapOf() + var byReforgeStone = mapOf() + var allReforges = listOf() + + fun findEligibleForItem(itemType: ItemType): List { + return byType[itemType] ?: listOf() + } + + fun findEligibleForInternalName(internalName: SkyblockId): List { + return byInternalName[internalName] ?: listOf() + } + + //TODO: return byVanillla + override fun reload(repo: NEURepository) { + val basicReforges = + repo.file("constants/reforges.json") + ?.kJson(serializer>()) + ?.values ?: emptyList() + val advancedReforges = + repo.file("constants/reforgestones.json") + ?.kJson(serializer>()) + ?.values ?: emptyList() + val allReforges = (basicReforges + advancedReforges) + modifierLut = allReforges.associateBy { it.reforgeId } + byReforgeStone = allReforges.filter { it.reforgeStone != null } + .associateBy { it.reforgeStone!! } + val byType = mutableMapOf>() + val byVanilla = mutableMapOf, MutableList>() + val byInternalName = mutableMapOf>() + this.byType = byType + this.byVanilla = byVanilla + this.byInternalName = byInternalName + for (reforge in allReforges) { + for (eligibleItem in reforge.eligibleItems) { + when (eligibleItem) { + is Reforge.ReforgeEligibilityFilter.AllowsInternalName -> { + byInternalName.getOrPut(eligibleItem.internalName, ::mutableListOf).add(reforge) + } + + is Reforge.ReforgeEligibilityFilter.AllowsItemType -> { + val actualItemTypes = resolveItemType(eligibleItem.itemType) + for (itemType in actualItemTypes) { + byType.getOrPut(itemType, ::mutableListOf).add(reforge) + } + } + + is Reforge.ReforgeEligibilityFilter.AllowsVanillaItemType -> { + byVanilla.getOrPut(eligibleItem.minecraftId, ::mutableListOf).add(reforge) + } + } + } + } + this.allReforges = allReforges + } + + fun resolveItemType(itemType: ItemType): List { + if (ItemType.SWORD == itemType) { + return listOf( + ItemType.SWORD, + ItemType.GAUNTLET, + ItemType.LONGSWORD,// TODO: check name + ItemType.FISHING_WEAPON,// TODO: check name + ) + } + if (itemType == ItemType.ofName("ARMOR")) { + return listOf( + ItemType.CHESTPLATE, + ItemType.LEGGINGS, + ItemType.HELMET, + ItemType.BOOTS, + ) + } + if (itemType == ItemType.EQUIPMENT) { + return listOf( + ItemType.CLOAK, + ItemType.BRACELET, + ItemType.NECKLACE, + ItemType.BELT, + ItemType.GLOVES, + ) + } + if (itemType == ItemType.ROD) { + return listOf(ItemType.FISHING_ROD, ItemType.FISHING_WEAPON) + } + return listOf(itemType) + } + + fun NEURepoFile.kJson(serializer: KSerializer): T { + val rawJson = json(JsonElement::class.java) + try { + val kJsonElement = JsonOps.INSTANCE.convertTo(KJsonOps.INSTANCE, rawJson) + return Firmament.json.decodeFromJsonElement(serializer, kJsonElement) + } catch (ex: Exception) { + throw NEURepositoryException(path, "Could not decode kotlin JSON element", ex) + } + } +} diff --git a/src/main/kotlin/repo/RepoItemTypeCache.kt b/src/main/kotlin/repo/RepoItemTypeCache.kt new file mode 100644 index 0000000..414ec09 --- /dev/null +++ b/src/main/kotlin/repo/RepoItemTypeCache.kt @@ -0,0 +1,15 @@ +package moe.nea.firmament.repo + +import io.github.moulberry.repo.IReloadable +import io.github.moulberry.repo.NEURepository +import io.github.moulberry.repo.data.NEUItem +import moe.nea.firmament.util.skyblock.ItemType + +object RepoItemTypeCache : IReloadable { + + var byItemType: Map> = mapOf() + + override fun reload(repository: NEURepository) { + byItemType = repository.items.items.values.groupBy { ItemType.fromEscapeCodeLore(it.lore.lastOrNull() ?: "") } + } +} diff --git a/src/main/kotlin/repo/RepoManager.kt b/src/main/kotlin/repo/RepoManager.kt index 667ab73..6d9ba14 100644 --- a/src/main/kotlin/repo/RepoManager.kt +++ b/src/main/kotlin/repo/RepoManager.kt @@ -53,13 +53,16 @@ object RepoManager { var recentlyFailedToUpdateItemList = false val essenceRecipeProvider = EssenceRecipeProvider() - val recipeCache = BetterRepoRecipeCache(essenceRecipeProvider) + val recipeCache = BetterRepoRecipeCache(essenceRecipeProvider, ReforgeStore) fun makeNEURepository(path: Path): NEURepository { return NEURepository.of(path).apply { registerReloadListener(ItemCache) + registerReloadListener(RepoItemTypeCache) registerReloadListener(ExpLadders) registerReloadListener(ItemNameLookup) + registerReloadListener(ReforgeStore) + registerReloadListener(essenceRecipeProvider) ReloadRegistrationEvent.publish(ReloadRegistrationEvent(this)) registerReloadListener { if (TestUtil.isInTest) return@registerReloadListener @@ -70,7 +73,6 @@ object RepoManager { } } } - registerReloadListener(essenceRecipeProvider) registerReloadListener(recipeCache) } } diff --git a/src/main/kotlin/repo/SBItemStack.kt b/src/main/kotlin/repo/SBItemStack.kt index 18126ee..e5cacaa 100644 --- a/src/main/kotlin/repo/SBItemStack.kt +++ b/src/main/kotlin/repo/SBItemStack.kt @@ -14,11 +14,16 @@ import net.minecraft.util.Formatting import moe.nea.firmament.repo.ItemCache.asItemStack import moe.nea.firmament.util.FirmFormatters import moe.nea.firmament.util.LegacyFormattingCode +import moe.nea.firmament.util.ReforgeId import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.getReforgeId +import moe.nea.firmament.util.getUpgradeStars import moe.nea.firmament.util.mc.appendLore import moe.nea.firmament.util.mc.displayNameAccordingToNbt +import moe.nea.firmament.util.mc.loreAccordingToNbt import moe.nea.firmament.util.petData import moe.nea.firmament.util.skyBlockId +import moe.nea.firmament.util.skyblock.ItemType import moe.nea.firmament.util.skyblockId import moe.nea.firmament.util.withColor @@ -28,8 +33,8 @@ data class SBItemStack constructor( private var stackSize: Int, private var petData: PetData?, val extraLore: List = emptyList(), - // TODO: grab this star data from nbt if possible val stars: Int = 0, + val reforge: ReforgeId? = null, ) { fun getStackSize() = stackSize @@ -66,7 +71,9 @@ data class SBItemStack constructor( skyblockId, RepoManager.getNEUItem(skyblockId), itemStack.count, - petData = itemStack.petData?.let { PetData.fromHypixel(it) } + petData = itemStack.petData?.let { PetData.fromHypixel(it) }, + stars = itemStack.getUpgradeStars(), + reforge = itemStack.getReforgeId() ) } @@ -127,6 +134,15 @@ data class SBItemStack constructor( } + private fun appendReforgeStatsToLore( + itemStack: ItemStack, + ) { + val rarity = itemStack.rarity + val lore = itemStack.loreAccordingToNbt + } + + // TODO: avoid instantiating the item stack here + val itemType: ItemType? get() = ItemType.fromItemStack(asImmutableItemStack()) private var itemStack_: ItemStack? = null private val itemStack: ItemStack @@ -141,6 +157,7 @@ data class SBItemStack constructor( return@run neuItem.asItemStack(idHint = skyblockId, replacementData) .copyWithCount(stackSize) .also { it.appendLore(extraLore) } + .also { if (reforge != null) it.appendLore(listOf(Text.literal("Reforge: $reforge"))) } // TODO: use this for proper rendering .also { enhanceStatsByStars(it, stars) } } if (itemStack_ == null) -- cgit From ddebaf47900dfab41590c97c202984142ae5b9f6 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Wed, 25 Dec 2024 16:15:20 +0100 Subject: WIP: Reforge Recipes --- .../moe/nea/firmament/compat/rei/EntityWidget.kt | 34 ++++-- .../compat/rei/recipes/SBReforgeRecipe.kt | 52 ++++++-- .../features/inventory/ItemRarityCosmetics.kt | 13 +- src/main/kotlin/gui/entity/EntityRenderer.kt | 13 +- src/main/kotlin/repo/ItemCache.kt | 2 + src/main/kotlin/repo/Reforge.kt | 14 ++- src/main/kotlin/repo/SBItemStack.kt | 136 ++++++++++++++++++++- src/main/kotlin/util/AprilFoolsUtil.kt | 10 ++ src/main/kotlin/util/FirmFormatters.kt | 14 ++- src/main/kotlin/util/SkyblockId.kt | 5 +- src/main/kotlin/util/mc/NbtItemData.kt | 4 +- src/main/kotlin/util/skyblock/Rarity.kt | 17 ++- src/main/kotlin/util/skyblock/SkyBlockItems.kt | 1 + src/main/kotlin/util/textutil.kt | 6 + 14 files changed, 273 insertions(+), 48 deletions(-) create mode 100644 src/main/kotlin/util/AprilFoolsUtil.kt (limited to 'src/main/kotlin/repo') diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/EntityWidget.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/EntityWidget.kt index d8238be..2b9e4bf 100644 --- a/src/compat/rei/java/moe/nea/firmament/compat/rei/EntityWidget.kt +++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/EntityWidget.kt @@ -1,19 +1,22 @@ package moe.nea.firmament.compat.rei import me.shedaniel.math.Dimension +import me.shedaniel.math.FloatingDimension import me.shedaniel.math.Point import me.shedaniel.math.Rectangle import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds import net.minecraft.client.gui.DrawContext -import net.minecraft.client.gui.Drawable import net.minecraft.client.gui.Element -import net.minecraft.client.gui.ParentElement import net.minecraft.entity.LivingEntity import moe.nea.firmament.gui.entity.EntityRenderer import moe.nea.firmament.util.ErrorUtil -class EntityWidget(val entity: LivingEntity?, val point: Point) : WidgetWithBounds() { +class EntityWidget( + val entity: LivingEntity?, + val point: Point, + val size: FloatingDimension = FloatingDimension(defaultSize) +) : WidgetWithBounds() { override fun children(): List { return emptyList() } @@ -22,18 +25,35 @@ class EntityWidget(val entity: LivingEntity?, val point: Point) : WidgetWithBoun override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { try { - if (!hasErrored) - EntityRenderer.renderEntity(entity!!, context, point.x, point.y, mouseX.toFloat(), mouseY.toFloat()) + context.matrices.push() + if (!hasErrored) { + context.matrices.translate(point.x.toDouble(), point.y.toDouble(), 0.0) + val xScale = size.width / defaultSize.width.toDouble() + val yScale = size.height / defaultSize.height.toDouble() + context.matrices.scale(xScale.toFloat(), yScale.toFloat(), 1.0F) + EntityRenderer.renderEntity( + entity!!, + context, + 0, 0, + (mouseX - point.x) * xScale, + (mouseY - point.y) * yScale) + } } catch (ex: Exception) { ErrorUtil.softError("Failed to render constructed entity: $entity", ex) hasErrored = true + } finally { + context.matrices.pop() } if (hasErrored) { - context.fill(point.x, point.y, point.x + 50, point.y + 80, 0xFFAA2222.toInt()) + context.fill(point.x, point.y, point.x + size.width.toInt(), point.y + size.height.toInt(), 0xFFAA2222.toInt()) } } + companion object { + val defaultSize = Dimension(50, 80) + } + override fun getBounds(): Rectangle { - return Rectangle(point, Dimension(50, 80)) + return Rectangle(point, size) } } diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBReforgeRecipe.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBReforgeRecipe.kt index 2805132..232f04f 100644 --- a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBReforgeRecipe.kt +++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBReforgeRecipe.kt @@ -1,6 +1,8 @@ package moe.nea.firmament.compat.rei.recipes import java.util.Optional +import me.shedaniel.math.Dimension +import me.shedaniel.math.FloatingDimension import me.shedaniel.math.Point import me.shedaniel.math.Rectangle import me.shedaniel.rei.api.client.gui.Renderer @@ -14,17 +16,27 @@ import me.shedaniel.rei.api.common.display.Display import me.shedaniel.rei.api.common.display.DisplaySerializer import me.shedaniel.rei.api.common.entry.EntryIngredient import me.shedaniel.rei.api.common.entry.EntryStack +import net.minecraft.entity.EntityType +import net.minecraft.entity.SpawnReason import net.minecraft.text.Text import net.minecraft.util.Identifier +import net.minecraft.village.VillagerProfession import moe.nea.firmament.Firmament +import moe.nea.firmament.compat.rei.EntityWidget import moe.nea.firmament.compat.rei.SBItemEntryDefinition +import moe.nea.firmament.gui.entity.EntityRenderer import moe.nea.firmament.repo.Reforge import moe.nea.firmament.repo.ReforgeStore import moe.nea.firmament.repo.RepoItemTypeCache import moe.nea.firmament.repo.RepoManager import moe.nea.firmament.repo.SBItemStack +import moe.nea.firmament.util.AprilFoolsUtil +import moe.nea.firmament.util.FirmFormatters import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.gold import moe.nea.firmament.util.skyblock.ItemType +import moe.nea.firmament.util.skyblock.Rarity +import moe.nea.firmament.util.skyblock.SkyBlockItems import moe.nea.firmament.util.skyblockId import moe.nea.firmament.util.tr @@ -46,21 +58,44 @@ class SBReforgeRecipe( } override fun getIcon(): Renderer { - return SBItemEntryDefinition.getEntry(SkyblockId("REFORGE_ANVIL")) + return SBItemEntryDefinition.getEntry(SkyBlockItems.REFORGE_ANVIL) } override fun setupDisplay(display: SBReforgeRecipe, bounds: Rectangle): MutableList { val list = mutableListOf() list.add(Widgets.createRecipeBase(bounds)) // TODO: actual layout after christmas, probably - list.add(Widgets.createSlot(Point(bounds.minX + 10, bounds.centerY)) + list.add(Widgets.createSlot(Point(bounds.minX + 10, bounds.centerY - 9)) .markInput().entries(display.inputItems)) - val stoneSlot = Widgets.createSlot(Point(bounds.minX + 38, bounds.centerY)) - .markInput() - if (display.reforgeStone != null) - stoneSlot.entry(display.reforgeStone) - list.add(stoneSlot) - list.add(Widgets.createSlot(Point(bounds.minX + 38 + 18, bounds.centerY)) + if (display.reforgeStone != null) { + list.add(Widgets.createSlot(Point(bounds.minX + 10 + 24, bounds.centerY - 9 - 10)) + .markInput().entry(display.reforgeStone)) + list.add(Widgets.withTooltip( + Widgets.withTranslate(Widgets.wrapRenderer( + Rectangle(Point(bounds.minX + 10 + 24, bounds.centerY - 9 + 10), Dimension(18, 18)), + SBItemEntryDefinition.getEntry(SkyBlockItems.REFORGE_ANVIL)), 0.0, 0.0, 150.0), + Rarity.entries.mapNotNull { rarity -> + display.reforge.reforgeCosts?.get(rarity)?.let { rarity to it } + }.map { (rarity, cost) -> + Text.literal("") + .append(rarity.text) + .append(": ") + .append(Text.literal("${FirmFormatters.formatCommas(cost, 0)} Coins").gold()) + } + )) + } else { + val size = if (AprilFoolsUtil.isAprilFoolsDay) 1.2 else 0.6 + val dimension = + FloatingDimension(EntityWidget.defaultSize.width * size, EntityWidget.defaultSize.height * size) + list.add(EntityWidget( + EntityType.VILLAGER.create(EntityRenderer.fakeWorld, SpawnReason.COMMAND) + ?.also { it.villagerData = it.villagerData.withProfession(VillagerProfession.WEAPONSMITH) }, + Point(bounds.minX + 10 + 24 + 8 - dimension.width / 2, bounds.centerY - dimension.height / 2), + dimension + )) +// TODO: render a blacksmith entity or smth + } + list.add(Widgets.createSlot(Point(bounds.minX + 10 + 24 + 24, bounds.centerY - 9)) .markInput().entries(display.outputItems)) return list } @@ -106,6 +141,7 @@ class SBReforgeRecipe( when (it) { is Reforge.ReforgeEligibilityFilter.AllowsInternalName -> listOfNotNull(RepoManager.getNEUItem(it.internalName)) + is Reforge.ReforgeEligibilityFilter.AllowsItemType -> ReforgeStore.resolveItemType(it.itemType) .flatMap { diff --git a/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt b/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt index d2c555b..fdc378a 100644 --- a/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt +++ b/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt @@ -29,18 +29,7 @@ object ItemRarityCosmetics : FirmamentFeature { override val config: ManagedConfig get() = TConfig - private val rarityToColor = mapOf( - Rarity.COMMON to Formatting.WHITE, - Rarity.UNCOMMON to Formatting.GREEN, - Rarity.RARE to Formatting.BLUE, - Rarity.EPIC to Formatting.DARK_PURPLE, - Rarity.LEGENDARY to Formatting.GOLD, - Rarity.MYTHIC to Formatting.LIGHT_PURPLE, - Rarity.DIVINE to Formatting.AQUA, - Rarity.SPECIAL to Formatting.RED, - Rarity.VERY_SPECIAL to Formatting.RED, - Rarity.SUPREME to Formatting.DARK_RED, - ).mapValues { + private val rarityToColor = Rarity.colourMap.mapValues { val c = Color(it.value.colorValue!!) c.rgb } diff --git a/src/main/kotlin/gui/entity/EntityRenderer.kt b/src/main/kotlin/gui/entity/EntityRenderer.kt index 022b9a3..9a09fc6 100644 --- a/src/main/kotlin/gui/entity/EntityRenderer.kt +++ b/src/main/kotlin/gui/entity/EntityRenderer.kt @@ -111,8 +111,9 @@ object EntityRenderer { renderContext: DrawContext, posX: Int, posY: Int, - mouseX: Float, - mouseY: Float + // TODO: Add width, height properties here + mouseX: Double, + mouseY: Double ) { var bottomOffset = 0.0F var currentEntity = entity @@ -148,15 +149,15 @@ object EntityRenderer { y2: Int, size: Float, bottomOffset: Float, - mouseX: Float, - mouseY: Float, + mouseX: Double, + mouseY: Double, entity: LivingEntity ) { context.enableScissorWithTranslation(x1.toFloat(), y1.toFloat(), x2.toFloat(), y2.toFloat()) val centerX = (x1 + x2) / 2f val centerY = (y1 + y2) / 2f - val targetYaw = atan(((centerX - mouseX) / 40.0f).toDouble()).toFloat() - val targetPitch = atan(((centerY - mouseY) / 40.0f).toDouble()).toFloat() + val targetYaw = atan(((centerX - mouseX) / 40.0f)).toFloat() + val targetPitch = atan(((centerY - mouseY) / 40.0f)).toFloat() val rotateToFaceTheFront = Quaternionf().rotateZ(Math.PI.toFloat()) val rotateToFaceTheCamera = Quaternionf().rotateX(targetPitch * 20.0f * (Math.PI.toFloat() / 180)) rotateToFaceTheFront.mul(rotateToFaceTheCamera) diff --git a/src/main/kotlin/repo/ItemCache.kt b/src/main/kotlin/repo/ItemCache.kt index f88dd48..9f1d45c 100644 --- a/src/main/kotlin/repo/ItemCache.kt +++ b/src/main/kotlin/repo/ItemCache.kt @@ -39,6 +39,7 @@ import moe.nea.firmament.util.TestUtil import moe.nea.firmament.util.directLiteralStringContent import moe.nea.firmament.util.mc.FirmamentDataComponentTypes import moe.nea.firmament.util.mc.appendLore +import moe.nea.firmament.util.mc.displayNameAccordingToNbt import moe.nea.firmament.util.mc.loreAccordingToNbt import moe.nea.firmament.util.mc.modifyLore import moe.nea.firmament.util.mc.setCustomName @@ -131,6 +132,7 @@ object ItemCache : IReloadable { val itemInstance = ItemStack.fromNbt(MC.defaultRegistries, modernItemTag).getOrNull() ?: return brokenItemStack(this) itemInstance.loreAccordingToNbt = lore.map { un189Lore(it) } + itemInstance.displayNameAccordingToNbt = un189Lore(displayName) val extraAttributes = oldItemTag.getCompound("tag").getCompound("ExtraAttributes") if (extraAttributes != null) itemInstance.set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(extraAttributes)) diff --git a/src/main/kotlin/repo/Reforge.kt b/src/main/kotlin/repo/Reforge.kt index ea01818..b52adc6 100644 --- a/src/main/kotlin/repo/Reforge.kt +++ b/src/main/kotlin/repo/Reforge.kt @@ -108,6 +108,8 @@ data class Reforge( @Serializable(with = RarityMapped.Serializer::class) sealed interface RarityMapped { + fun get(rarity: Rarity): T? + class Serializer( val values: KSerializer ) : KSerializer> { @@ -137,10 +139,18 @@ data class Reforge( } @Serializable - data class Direct(val value: T) : RarityMapped + data class Direct(val value: T) : RarityMapped { + override fun get(rarity: Rarity): T { + return value + } + } @Serializable - data class PerRarity(val values: Map) : RarityMapped + data class PerRarity(val values: Map) : RarityMapped { + override fun get(rarity: Rarity): T? { + return values[rarity] + } + } } } diff --git a/src/main/kotlin/repo/SBItemStack.kt b/src/main/kotlin/repo/SBItemStack.kt index e5cacaa..a5f54ae 100644 --- a/src/main/kotlin/repo/SBItemStack.kt +++ b/src/main/kotlin/repo/SBItemStack.kt @@ -9,22 +9,31 @@ import net.minecraft.item.ItemStack import net.minecraft.network.RegistryByteBuf import net.minecraft.network.codec.PacketCodec import net.minecraft.network.codec.PacketCodecs +import net.minecraft.text.Style import net.minecraft.text.Text +import net.minecraft.text.TextColor import net.minecraft.util.Formatting import moe.nea.firmament.repo.ItemCache.asItemStack import moe.nea.firmament.util.FirmFormatters import moe.nea.firmament.util.LegacyFormattingCode import moe.nea.firmament.util.ReforgeId import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.blue +import moe.nea.firmament.util.directLiteralStringContent +import moe.nea.firmament.util.extraAttributes import moe.nea.firmament.util.getReforgeId import moe.nea.firmament.util.getUpgradeStars +import moe.nea.firmament.util.grey import moe.nea.firmament.util.mc.appendLore import moe.nea.firmament.util.mc.displayNameAccordingToNbt import moe.nea.firmament.util.mc.loreAccordingToNbt import moe.nea.firmament.util.petData +import moe.nea.firmament.util.prepend import moe.nea.firmament.util.skyBlockId import moe.nea.firmament.util.skyblock.ItemType +import moe.nea.firmament.util.skyblock.Rarity import moe.nea.firmament.util.skyblockId +import moe.nea.firmament.util.useMatch import moe.nea.firmament.util.withColor data class SBItemStack constructor( @@ -84,6 +93,117 @@ data class SBItemStack constructor( } return SBItemStack(neuIngredient.skyblockId, neuIngredient.amount.toInt()) } + + fun appendEnhancedStats( + itemStack: ItemStack, + reforgeStats: Map, + buffKind: BuffKind, + ) { + val namedReforgeStats = reforgeStats + .mapKeysTo(mutableMapOf()) { statIdToName(it.key) } + val loreMut = itemStack.loreAccordingToNbt.toMutableList() + var statBlockLastIndex = -1 + for (i in loreMut.indices) { + val statLine = parseStatLine(loreMut[i]) + if (statLine == null && statBlockLastIndex >= 0) { + break + } + if (statLine == null) { + continue + } + statBlockLastIndex = i + val statBuff = namedReforgeStats.remove(statLine.statName) ?: continue + loreMut[i] = statLine.addStat(statBuff, buffKind).reconstitute() + } + if (namedReforgeStats.isNotEmpty() && statBlockLastIndex == -1) { + loreMut.add(0, Text.literal("")) + } + // If there is no stat block the statBlockLastIndex falls through to -1 + // TODO: this is good enough for some items. some other items might have their stats at a different place. + for ((statName, statBuff) in namedReforgeStats) { + val statLine = StatLine(statName, null).addStat(statBuff, buffKind) + loreMut.add(statBlockLastIndex + 1, statLine.reconstitute()) + } + itemStack.loreAccordingToNbt = loreMut + } + + data class StatFormatting( + val postFix: String, + val color: Formatting, + ) + + val formattingOverrides = mapOf( + "Sea Creature Chance" to StatFormatting("%", Formatting.RED), + "Strength" to StatFormatting("", Formatting.RED), + "Damage" to StatFormatting("", Formatting.RED), + "Bonus Attack Speed" to StatFormatting("%", Formatting.RED), + "Shot Cooldown" to StatFormatting("s", Formatting.RED), + "Ability Damage" to StatFormatting("%", Formatting.RED), + "Crit Damage" to StatFormatting("%", Formatting.RED), + "Crit Chance" to StatFormatting("%", Formatting.RED), + "Trophy Fish Chance" to StatFormatting("%", Formatting.GREEN), + // TODO: add other types and make this a repo json + ) + + + private val statLabelRegex = "(?.*): ".toPattern() + + enum class BuffKind( + val color: Formatting, + val prefix: String, + val postFix: String, + ) { + REFORGE(Formatting.BLUE, "(", ")"), + + ; + } + + data class StatLine( + val statName: String, + val value: Text?, + val rest: List = listOf(), + val valueNum: Double? = value?.directLiteralStringContent?.trim(' ', '%', '+')?.toDoubleOrNull() + ) { + fun addStat(amount: Double, buffKind: BuffKind): StatLine { + val formattedAmount = FirmFormatters.formatCommas(amount, 1, includeSign = true) + return copy( + valueNum = (valueNum ?: 0.0) + amount, + value = null, + rest = rest + + listOf( + Text.literal( + buffKind.prefix + formattedAmount + + statFormatting.postFix + + buffKind.postFix + " ") + .withColor(buffKind.color))) + } + + fun formatValue() = + 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) + fun reconstitute(): Text = + Text.literal("").setStyle(Style.EMPTY.withItalic(false)) + .append(Text.literal("$statName: ").grey()) + .append(value ?: formatValue()) + .also { rest.forEach(it::append) } + } + + private fun statIdToName(statId: String): String { + return statId.split("_").joinToString(" ") { + it.replaceFirstChar { it.uppercaseChar() } + } + } + + private 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 + val statLabel = stat.directLiteralStringContent ?: return null + val statName = statLabelRegex.useMatch(statLabel) { group("statName") } ?: return null + return StatLine(statName, sibs[1], sibs.subList(2, sibs.size)) + } } constructor(skyblockId: SkyblockId, petData: PetData) : this( @@ -134,11 +254,19 @@ data class SBItemStack constructor( } - private fun appendReforgeStatsToLore( + private fun appendReforgeInfo( itemStack: ItemStack, ) { - val rarity = itemStack.rarity - val lore = itemStack.loreAccordingToNbt + val rarity = Rarity.fromItem(itemStack) ?: return + val reforgeId = this.reforge ?: return + val reforge = ReforgeStore.modifierLut[reforgeId] ?: return + val reforgeStats = reforge.reforgeStats?.get(rarity) ?: mapOf() + itemStack.displayNameAccordingToNbt = itemStack.displayNameAccordingToNbt.copy() + .prepend(Text.literal(reforge.reforgeName + " ").formatted(Rarity.colourMap[rarity] ?: Formatting.WHITE)) + val data = itemStack.extraAttributes.copy() + data.putString("modifier", reforgeId.id) + itemStack.extraAttributes = data + appendEnhancedStats(itemStack, reforgeStats, BuffKind.REFORGE) } // TODO: avoid instantiating the item stack here @@ -156,8 +284,8 @@ data class SBItemStack constructor( injectReplacementDataForPets(replacementData) return@run neuItem.asItemStack(idHint = skyblockId, replacementData) .copyWithCount(stackSize) + .also { appendReforgeInfo(it) } .also { it.appendLore(extraLore) } - .also { if (reforge != null) it.appendLore(listOf(Text.literal("Reforge: $reforge"))) } // TODO: use this for proper rendering .also { enhanceStatsByStars(it, stars) } } if (itemStack_ == null) diff --git a/src/main/kotlin/util/AprilFoolsUtil.kt b/src/main/kotlin/util/AprilFoolsUtil.kt new file mode 100644 index 0000000..a940fa1 --- /dev/null +++ b/src/main/kotlin/util/AprilFoolsUtil.kt @@ -0,0 +1,10 @@ +package moe.nea.firmament.util + +import java.time.LocalDateTime +import java.time.Month + +object AprilFoolsUtil { + val isAprilFoolsDay = LocalDateTime.now().let { + it.dayOfMonth == 1 && it.month == Month.APRIL + } +} diff --git a/src/main/kotlin/util/FirmFormatters.kt b/src/main/kotlin/util/FirmFormatters.kt index 92fb9e5..4b32c2a 100644 --- a/src/main/kotlin/util/FirmFormatters.kt +++ b/src/main/kotlin/util/FirmFormatters.kt @@ -15,21 +15,25 @@ import net.minecraft.text.Text object FirmFormatters { fun formatCommas(int: Int, segments: Int = 3): String = formatCommas(int.toLong(), segments) - fun formatCommas(long: Long, segments: Int = 3): String { + fun formatCommas(long: Long, segments: Int = 3, includeSign: Boolean = false): String { + if (long < 0 && long != Long.MIN_VALUE) { + return "-" + formatCommas(-long, segments, false) + } + val prefix = if (includeSign) "+" else "" val α = long / 1000 if (α != 0L) { - return formatCommas(α, segments) + "," + (long - α * 1000).toString().padStart(3, '0') + return prefix + formatCommas(α, segments) + "," + (long - α * 1000).toString().padStart(3, '0') } - return long.toString() + return prefix + long.toString() } fun formatCommas(float: Float, fractionalDigits: Int): String = formatCommas(float.toDouble(), fractionalDigits) - fun formatCommas(double: Double, fractionalDigits: Int): String { + fun formatCommas(double: Double, fractionalDigits: Int, includeSign: Boolean = false): String { val long = double.toLong() val δ = (double - long).absoluteValue val μ = pow(10, fractionalDigits) val digits = (μ * δ).toInt().toString().padStart(fractionalDigits, '0').trimEnd('0') - return formatCommas(long) + (if (digits.isEmpty()) "" else ".$digits") + return formatCommas(long, includeSign = includeSign) + (if (digits.isEmpty()) "" else ".$digits") } fun formatDistance(distance: Double): String { diff --git a/src/main/kotlin/util/SkyblockId.kt b/src/main/kotlin/util/SkyblockId.kt index 497a2d2..631b444 100644 --- a/src/main/kotlin/util/SkyblockId.kt +++ b/src/main/kotlin/util/SkyblockId.kt @@ -106,7 +106,10 @@ data class HypixelPetInfo( private val jsonparser = Json { ignoreUnknownKeys = true } -val ItemStack.extraAttributes: NbtCompound +var ItemStack.extraAttributes: NbtCompound + set(value) { + set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(value)) + } get() { val customData = get(DataComponentTypes.CUSTOM_DATA) ?: run { val component = NbtComponent.of(NbtCompound()) diff --git a/src/main/kotlin/util/mc/NbtItemData.kt b/src/main/kotlin/util/mc/NbtItemData.kt index e8a908f..0c49862 100644 --- a/src/main/kotlin/util/mc/NbtItemData.kt +++ b/src/main/kotlin/util/mc/NbtItemData.kt @@ -5,8 +5,8 @@ import net.minecraft.component.type.LoreComponent import net.minecraft.item.ItemStack import net.minecraft.text.Text -var ItemStack.loreAccordingToNbt - get() = get(DataComponentTypes.LORE)?.lines ?: listOf() +var ItemStack.loreAccordingToNbt: List + get() = get(DataComponentTypes.LORE)?.lines ?: listOf() set(value) { set(DataComponentTypes.LORE, LoreComponent(value)) } diff --git a/src/main/kotlin/util/skyblock/Rarity.kt b/src/main/kotlin/util/skyblock/Rarity.kt index 0244630..b19f371 100644 --- a/src/main/kotlin/util/skyblock/Rarity.kt +++ b/src/main/kotlin/util/skyblock/Rarity.kt @@ -8,7 +8,9 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import net.minecraft.item.ItemStack +import net.minecraft.text.Style import net.minecraft.text.Text +import net.minecraft.util.Formatting import moe.nea.firmament.util.StringUtil.words import moe.nea.firmament.util.collections.lastNotNullOfOrNull import moe.nea.firmament.util.mc.loreAccordingToNbt @@ -46,10 +48,23 @@ enum class Rarity(vararg altNames: String) { } val names = setOf(name) + altNames - + val text: Text get() = Text.literal(name).setStyle(Style.EMPTY.withColor(colourMap[this])) val neuRepoRarity: RepoRarity? = RepoRarity.entries.find { it.name == name } companion object { + // TODO: inline those formattings as fields + val colourMap = mapOf( + Rarity.COMMON to Formatting.WHITE, + Rarity.UNCOMMON to Formatting.GREEN, + Rarity.RARE to Formatting.BLUE, + Rarity.EPIC to Formatting.DARK_PURPLE, + Rarity.LEGENDARY to Formatting.GOLD, + Rarity.MYTHIC to Formatting.LIGHT_PURPLE, + Rarity.DIVINE to Formatting.AQUA, + Rarity.SPECIAL to Formatting.RED, + Rarity.VERY_SPECIAL to Formatting.RED, + Rarity.SUPREME to Formatting.DARK_RED, + ) val byName = entries.flatMap { en -> en.names.map { it to en } }.toMap() val fromNeuRepo = entries.associateBy { it.neuRepoRarity } diff --git a/src/main/kotlin/util/skyblock/SkyBlockItems.kt b/src/main/kotlin/util/skyblock/SkyBlockItems.kt index c94ebfe..cfd8429 100644 --- a/src/main/kotlin/util/skyblock/SkyBlockItems.kt +++ b/src/main/kotlin/util/skyblock/SkyBlockItems.kt @@ -7,4 +7,5 @@ object SkyBlockItems { val ENCHANTED_DIAMOND = SkyblockId("ENCHANTED_DIAMOND") val DIAMOND = SkyblockId("DIAMOND") val ANCESTRAL_SPADE = SkyblockId("ANCESTRAL_SPADE") + val REFORGE_ANVIL = SkyblockId("REFORGE_ANVIL") } diff --git a/src/main/kotlin/util/textutil.kt b/src/main/kotlin/util/textutil.kt index 06ed8c8..ab3de43 100644 --- a/src/main/kotlin/util/textutil.kt +++ b/src/main/kotlin/util/textutil.kt @@ -133,6 +133,7 @@ fun MutableText.darkGreen() = withColor(Formatting.DARK_GREEN) fun MutableText.purple() = withColor(Formatting.DARK_PURPLE) fun MutableText.pink() = withColor(Formatting.LIGHT_PURPLE) fun MutableText.yellow() = withColor(Formatting.YELLOW) +fun MutableText.gold() = withColor(Formatting.GOLD) fun MutableText.grey() = withColor(Formatting.GRAY) fun MutableText.red() = withColor(Formatting.RED) fun MutableText.white() = withColor(Formatting.WHITE) @@ -146,6 +147,11 @@ fun MutableText.clickCommand(command: String): MutableText { } } +fun MutableText.prepend(text: Text): MutableText { + siblings.addFirst(text) + return this +} + fun Text.transformEachRecursively(function: (Text) -> Text): Text { val c = this.content if (c is TranslatableTextContent) { -- cgit From c4997e21053c8671ed4c5474fb70df6ae58dae06 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Wed, 25 Dec 2024 16:55:33 +0100 Subject: feat: Add reforge recipes --- build.gradle.kts | 1 + .../compat/rei/recipes/SBReforgeRecipe.kt | 47 +++++++++++++++++----- src/main/kotlin/repo/Reforge.kt | 10 +++-- src/main/kotlin/repo/SBItemStack.kt | 26 ++++++++---- translations/en_us.json | 2 + 5 files changed, 65 insertions(+), 21 deletions(-) (limited to 'src/main/kotlin/repo') diff --git a/build.gradle.kts b/build.gradle.kts index 1d5478f..15b7d71 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -182,6 +182,7 @@ fun createIsolatedSourceSet(name: String, path: String = "compat/$name", isEnabl classpath.from(configurations.getByName(ss.compileClasspathConfigurationName)) } collectTranslations { + // TODO: this does not work, somehow this.classes.from(sourceSets.main.get().kotlin.classesDirectory) } return ss diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBReforgeRecipe.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBReforgeRecipe.kt index 232f04f..9c8d8f4 100644 --- a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBReforgeRecipe.kt +++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBReforgeRecipe.kt @@ -6,6 +6,7 @@ import me.shedaniel.math.FloatingDimension import me.shedaniel.math.Point import me.shedaniel.math.Rectangle import me.shedaniel.rei.api.client.gui.Renderer +import me.shedaniel.rei.api.client.gui.widgets.Label import me.shedaniel.rei.api.client.gui.widgets.Widget import me.shedaniel.rei.api.client.gui.widgets.Widgets import me.shedaniel.rei.api.client.registry.display.DisplayCategory @@ -34,6 +35,7 @@ import moe.nea.firmament.util.AprilFoolsUtil import moe.nea.firmament.util.FirmFormatters import moe.nea.firmament.util.SkyblockId import moe.nea.firmament.util.gold +import moe.nea.firmament.util.grey import moe.nea.firmament.util.skyblock.ItemType import moe.nea.firmament.util.skyblock.Rarity import moe.nea.firmament.util.skyblock.SkyBlockItems @@ -64,15 +66,15 @@ class SBReforgeRecipe( override fun setupDisplay(display: SBReforgeRecipe, bounds: Rectangle): MutableList { val list = mutableListOf() list.add(Widgets.createRecipeBase(bounds)) - // TODO: actual layout after christmas, probably - list.add(Widgets.createSlot(Point(bounds.minX + 10, bounds.centerY - 9)) - .markInput().entries(display.inputItems)) + val inputSlot = Widgets.createSlot(Point(bounds.minX + 10, bounds.centerY - 9)) + .markInput().entries(display.inputItems) + list.add(inputSlot) if (display.reforgeStone != null) { list.add(Widgets.createSlot(Point(bounds.minX + 10 + 24, bounds.centerY - 9 - 10)) .markInput().entry(display.reforgeStone)) list.add(Widgets.withTooltip( Widgets.withTranslate(Widgets.wrapRenderer( - Rectangle(Point(bounds.minX + 10 + 24, bounds.centerY - 9 + 10), Dimension(18, 18)), + Rectangle(Point(bounds.minX + 10 + 24, bounds.centerY - 9 + 10), Dimension(16, 16)), SBItemEntryDefinition.getEntry(SkyBlockItems.REFORGE_ANVIL)), 0.0, 0.0, 150.0), Rarity.entries.mapNotNull { rarity -> display.reforge.reforgeCosts?.get(rarity)?.let { rarity to it } @@ -87,16 +89,41 @@ class SBReforgeRecipe( val size = if (AprilFoolsUtil.isAprilFoolsDay) 1.2 else 0.6 val dimension = FloatingDimension(EntityWidget.defaultSize.width * size, EntityWidget.defaultSize.height * size) - list.add(EntityWidget( - EntityType.VILLAGER.create(EntityRenderer.fakeWorld, SpawnReason.COMMAND) - ?.also { it.villagerData = it.villagerData.withProfession(VillagerProfession.WEAPONSMITH) }, - Point(bounds.minX + 10 + 24 + 8 - dimension.width / 2, bounds.centerY - dimension.height / 2), - dimension + list.add(Widgets.withTooltip( + EntityWidget( + EntityType.VILLAGER.create(EntityRenderer.fakeWorld, SpawnReason.COMMAND) + ?.also { it.villagerData = it.villagerData.withProfession(VillagerProfession.WEAPONSMITH) }, + Point(bounds.minX + 10 + 24 + 8 - dimension.width / 2, bounds.centerY - dimension.height / 2), + dimension + ), + tr("firmament.recipecategory.reforge.basic", + "This is a basic reforge, available at the Blacksmith.").grey() )) -// TODO: render a blacksmith entity or smth } list.add(Widgets.createSlot(Point(bounds.minX + 10 + 24 + 24, bounds.centerY - 9)) .markInput().entries(display.outputItems)) + val statToLineMappings = mutableListOf>() + for ((i, statId) in display.reforge.statUniverse.withIndex()) { + val label = Widgets.createLabel( + Point(bounds.minX + 10 + 24 + 24 + 20, bounds.minY + 8 + i * 11), + SBItemStack.Companion.StatLine(SBItemStack.statIdToName(statId), null).reconstitute(7)) + .horizontalAlignment(Label.LEFT_ALIGNED) + statToLineMappings.add(statId to label) + list.add(label) + } + fun updateStatLines() { + val entry = inputSlot.currentEntry?.castValue() ?: return + val stats = display.reforge.reforgeStats?.get(entry.rarity) ?: mapOf() + for ((stat, label) in statToLineMappings) { + label.message = + SBItemStack.Companion.StatLine( + SBItemStack.statIdToName(stat), null, + valueNum = stats[stat] + ).reconstitute(7) + } + } + updateStatLines() + inputSlot.withEntriesListener { updateStatLines() } return list } } diff --git a/src/main/kotlin/repo/Reforge.kt b/src/main/kotlin/repo/Reforge.kt index b52adc6..dc0d93d 100644 --- a/src/main/kotlin/repo/Reforge.kt +++ b/src/main/kotlin/repo/Reforge.kt @@ -36,6 +36,10 @@ data class Reforge( ) { val eligibleItems get() = allowOn ?: itemTypes ?: listOf() + val statUniverse: Set = Rarity.entries.flatMapTo(mutableSetOf()) { + reforgeStats?.get(it)?.keys ?: emptySet() + } + @Serializable(with = ReforgeEligibilityFilter.Serializer::class) sealed interface ReforgeEligibilityFilter { object ItemTypesSerializer : KSerializer> { @@ -108,7 +112,7 @@ data class Reforge( @Serializable(with = RarityMapped.Serializer::class) sealed interface RarityMapped { - fun get(rarity: Rarity): T? + fun get(rarity: Rarity?): T? class Serializer( val values: KSerializer @@ -140,14 +144,14 @@ data class Reforge( @Serializable data class Direct(val value: T) : RarityMapped { - override fun get(rarity: Rarity): T { + override fun get(rarity: Rarity?): T { return value } } @Serializable data class PerRarity(val values: Map) : RarityMapped { - override fun get(rarity: Rarity): T? { + override fun get(rarity: Rarity?): T? { return values[rarity] } } diff --git a/src/main/kotlin/repo/SBItemStack.kt b/src/main/kotlin/repo/SBItemStack.kt index a5f54ae..3c59543 100644 --- a/src/main/kotlin/repo/SBItemStack.kt +++ b/src/main/kotlin/repo/SBItemStack.kt @@ -18,7 +18,6 @@ import moe.nea.firmament.util.FirmFormatters import moe.nea.firmament.util.LegacyFormattingCode import moe.nea.firmament.util.ReforgeId import moe.nea.firmament.util.SkyblockId -import moe.nea.firmament.util.blue import moe.nea.firmament.util.directLiteralStringContent import moe.nea.firmament.util.extraAttributes import moe.nea.firmament.util.getReforgeId @@ -179,21 +178,30 @@ data class SBItemStack constructor( } 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) - fun reconstitute(): Text = + private fun abbreviate(abbreviateTo: Int): String { + if (abbreviateTo >= statName.length) return statName + val segments = statName.split(" ") + return segments.joinToString(" ") { + it.substring(0, maxOf(1, abbreviateTo / segments.size)) + } + } + + fun reconstitute(abbreviateTo: Int = Int.MAX_VALUE): Text = Text.literal("").setStyle(Style.EMPTY.withItalic(false)) - .append(Text.literal("$statName: ").grey()) + .append(Text.literal("${abbreviate(abbreviateTo)}: ").grey()) .append(value ?: formatValue()) .also { rest.forEach(it::append) } } - private fun statIdToName(statId: String): String { - return statId.split("_").joinToString(" ") { - it.replaceFirstChar { it.uppercaseChar() } - } + fun statIdToName(statId: String): String { + val segments = statId.split("_") + return segments.joinToString(" ") { it.replaceFirstChar { it.uppercaseChar() } } } private fun parseStatLine(line: Text): StatLine? { @@ -271,6 +279,8 @@ data class SBItemStack constructor( // TODO: avoid instantiating the item stack here val itemType: ItemType? get() = ItemType.fromItemStack(asImmutableItemStack()) + val rarity: Rarity? get() = Rarity.fromItem(asImmutableItemStack()) + private var itemStack_: ItemStack? = null private val itemStack: ItemStack diff --git a/translations/en_us.json b/translations/en_us.json index 3d8de96..82dcd45 100644 --- a/translations/en_us.json +++ b/translations/en_us.json @@ -331,6 +331,8 @@ "firmament.recipe.mobs.name": "§8[§7Lv %d§8] §c%s", "firmament.recipe.mobs.name.nolevel": "§c%s", "firmament.recipe.novanilla": "Hypixel cannot super craft vanilla recipes", + "firmament.recipecategory.reforge": "Reforge", + "firmament.recipecategory.reforge.basic": "This is a basic reforge, available at the Blacksmith.", "firmament.reiwarning": "Firmament needs RoughlyEnoughItems to display its item list!", "firmament.reiwarning.disable": "Click here to disable this warning", "firmament.reiwarning.disabled": "Disabled the RoughlyEnoughItems warning. Keep in mind that you will not have an item list without REI.", -- cgit From d49bfe885bca2f3dc88639e5bf1fd0d488ebde32 Mon Sep 17 00:00:00 2001 From: jani270 <69345714+jani270@users.noreply.github.com> Date: Wed, 25 Dec 2024 16:56:32 +0100 Subject: feat: More Stats for the reforge stats (#54) --- src/main/kotlin/repo/SBItemStack.kt | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) (limited to 'src/main/kotlin/repo') diff --git a/src/main/kotlin/repo/SBItemStack.kt b/src/main/kotlin/repo/SBItemStack.kt index 3c59543..20f2618 100644 --- a/src/main/kotlin/repo/SBItemStack.kt +++ b/src/main/kotlin/repo/SBItemStack.kt @@ -140,8 +140,35 @@ data class SBItemStack constructor( "Ability Damage" to StatFormatting("%", Formatting.RED), "Crit Damage" to StatFormatting("%", Formatting.RED), "Crit Chance" to StatFormatting("%", Formatting.RED), + "Ability Damage" to StatFormatting("%", Formatting.RED), "Trophy Fish Chance" to StatFormatting("%", Formatting.GREEN), - // TODO: add other types and make this a repo json + "Health" to StatFormatting("", Formatting.GREEN), + "Defense" to StatFormatting("", Formatting.GREEN), + "Fishing Speed" to StatFormatting("", Formatting.GREEN), + "Double Hook Chance" to StatFormatting("%", Formatting.GREEN), + "Mining Speed" to StatFormatting("", Formatting.GREEN), + "Mining Fortune" to StatFormatting("", Formatting.GREEN), + "Heat Resistance" to StatFormatting("", Formatting.GREEN), + "Swing Range" to StatFormatting("", Formatting.GREEN), + "Rift Time" to StatFormatting("", Formatting.GREEN), + "Speed" to StatFormatting("", Formatting.GREEN), + "Farming Fortune" to StatFormatting("", Formatting.GREEN), + "True Defense" to StatFormatting("", Formatting.GREEN), + "Mending" to StatFormatting("", Formatting.GREEN), + "Foraging Wisdom" to StatFormatting("", Formatting.GREEN), + "Farming Wisdom" to StatFormatting("", Formatting.GREEN), + "Foraging Fortune" to StatFormatting("", Formatting.GREEN), + "Magic Find" to StatFormatting("", Formatting.GREEN), + "Ferocity" to StatFormatting("", Formatting.GREEN), + "Bonus Pest Chance" to StatFormatting("%", Formatting.GREEN), + "Cold Resistance" to StatFormatting("", Formatting.GREEN), + "Pet Luck" to StatFormatting("", Formatting.GREEN), + "Fear" to StatFormatting("", Formatting.GREEN), + "Mana Regen" to StatFormatting("%", Formatting.GREEN), + "Rift Damage" to StatFormatting("", Formatting.GREEN), + "Hearts" to StatFormatting("", Formatting.GREEN), + "Vitality" to StatFormatting("", Formatting.GREEN), + // TODO: make this a repo json ) -- cgit