Merge branch 'mc-1.21.3'

This commit is contained in:
Linnea Gräf
2024-12-31 16:52:29 +01:00
53 changed files with 2182 additions and 370 deletions

View File

@@ -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<SkyblockId, Set<NEURecipe>> = mapOf()
var recipes: Map<SkyblockId, Set<NEURecipe>> = mapOf()
class BetterRepoRecipeCache(vararg val extraProviders: ExtraRecipeProvider) : IReloadable {
var usages: Map<SkyblockId, Set<NEURecipe>> = mapOf()
var recipes: Map<SkyblockId, Set<NEURecipe>> = mapOf()
override fun reload(repository: NEURepository) {
val usages = mutableMapOf<SkyblockId, MutableSet<NEURecipe>>()
val recipes = mutableMapOf<SkyblockId, MutableSet<NEURecipe>>()
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<SkyblockId, MutableSet<NEURecipe>>()
val recipes = mutableMapOf<SkyblockId, MutableSet<NEURecipe>>()
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
}
}

View File

@@ -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<NEUIngredient>,
) : 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<NEUIngredient>,
) : NEURecipe {
val essenceIngredient = NEUIngredient.fromString("${essenceType}:$essenceCost")
val allUpgradeComponents = listOf(essenceIngredient) + extraItems
override fun getAllInputs(): Collection<NEUIngredient> {
return listOf(NEUIngredient.fromString(itemId.neuItem + ":1")) + allUpgradeComponents
}
override fun getAllInputs(): Collection<NEUIngredient> {
return listOf(NEUIngredient.fromString(itemId.neuItem + ":1")) + allUpgradeComponents
}
override fun getAllOutputs(): Collection<NEUIngredient> {
return listOf(NEUIngredient.fromString(itemId.neuItem + ":1"))
}
}
override fun getAllOutputs(): Collection<NEUIngredient> {
return listOf(NEUIngredient.fromString(itemId.neuItem + ":1"))
}
}
var recipes = listOf<EssenceUpgradeRecipe>()
private set
var recipes = listOf<EssenceUpgradeRecipe>()
private set
override fun reload(repository: NEURepository) {
val recipes = mutableListOf<EssenceUpgradeRecipe>()
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 provideExtraRecipes(): Iterable<NEURecipe> = recipes
override fun reload(repository: NEURepository) {
val recipes = mutableListOf<EssenceUpgradeRecipe>()
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
}
}

View File

@@ -0,0 +1,7 @@
package moe.nea.firmament.repo
import io.github.moulberry.repo.data.NEURecipe
interface ExtraRecipeProvider {
fun provideExtraRecipes(): Iterable<NEURecipe>
}

View File

@@ -24,21 +24,27 @@ 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.displayNameAccordingToNbt
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<String, ItemStack> = ConcurrentHashMap()
@@ -94,6 +100,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()
@@ -101,6 +136,8 @@ object ItemCache : IReloadable {
?: return brokenItemStack(this)
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))
@@ -135,12 +172,13 @@ object ItemCache : IReloadable {
}
fun Text.applyLoreReplacements(loreReplacements: Map<String, String>): 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

View File

@@ -0,0 +1,160 @@
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<Rarity>? = null,
val itemTypes: @Serializable(with = ReforgeEligibilityFilter.ItemTypesSerializer::class) List<ReforgeEligibilityFilter>? = null,
val allowOn: List<ReforgeEligibilityFilter>? = null,
val reforgeCosts: RarityMapped<Double>? = null,
val reforgeAbility: RarityMapped<String>? = null,
val reforgeStats: RarityMapped<Map<String, Double>>? = null,
) {
val eligibleItems get() = allowOn ?: itemTypes ?: listOf()
val statUniverse: Set<String> = Rarity.entries.flatMapTo(mutableSetOf()) {
reforgeStats?.get(it)?.keys ?: emptySet()
}
@Serializable(with = ReforgeEligibilityFilter.Serializer::class)
sealed interface ReforgeEligibilityFilter {
object ItemTypesSerializer : KSerializer<List<ReforgeEligibilityFilter>> {
override val descriptor: SerialDescriptor
get() = JsonElement.serializer().descriptor
override fun deserialize(decoder: Decoder): List<ReforgeEligibilityFilter> {
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<List<ReforgeEligibilityFilter>>(), jsonElement)
}
jsonElement as JsonObject
val filters = mutableListOf<ReforgeEligibilityFilter>()
jsonElement["internalName"]?.let {
decoder.json.decodeFromJsonElement(serializer<List<SkyblockId>>(), it).forEach {
filters.add(AllowsInternalName(it))
}
}
jsonElement["itemId"]?.let {
decoder.json.decodeFromJsonElement(serializer<List<String>>(), 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<ReforgeEligibilityFilter>) {
TODO("Not yet implemented")
}
}
object Serializer : KSerializer<ReforgeEligibilityFilter> {
override val descriptor: SerialDescriptor
get() = serializer<JsonElement>().descriptor
override fun deserialize(decoder: Decoder): ReforgeEligibilityFilter {
val jsonObject = serializer<JsonObject>().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<Item>) : ReforgeEligibilityFilter
}
val reforgeId get() = nbtModifier ?: ReforgeId(reforgeName.lowercase())
@Serializable(with = RarityMapped.Serializer::class)
sealed interface RarityMapped<T> {
fun get(rarity: Rarity?): T?
class Serializer<T>(
val values: KSerializer<T>
) : KSerializer<RarityMapped<T>> {
override val descriptor: SerialDescriptor
get() = JsonElement.serializer().descriptor
val indirect = MapSerializer(Rarity.serializer(), values)
override fun deserialize(decoder: Decoder): RarityMapped<T> {
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<T>) {
when (value) {
is Direct<T> ->
values.serialize(encoder, value.value)
is PerRarity<T> ->
indirect.serialize(encoder, value.values)
}
}
}
@Serializable
data class Direct<T>(val value: T) : RarityMapped<T> {
override fun get(rarity: Rarity?): T {
return value
}
}
@Serializable
data class PerRarity<T>(val values: Map<Rarity, T>) : RarityMapped<T> {
override fun get(rarity: Rarity?): T? {
return values[rarity]
}
}
}
}

View File

@@ -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<NEURecipe> {
return emptyList()
}
var byType: Map<ItemType, List<Reforge>> = mapOf()
var byVanilla: Map<RegistryKey<Item>, List<Reforge>> = mapOf()
var byInternalName: Map<SkyblockId, List<Reforge>> = mapOf()
var modifierLut = mapOf<ReforgeId, Reforge>()
var byReforgeStone = mapOf<SkyblockId, Reforge>()
var allReforges = listOf<Reforge>()
fun findEligibleForItem(itemType: ItemType): List<Reforge> {
return byType[itemType] ?: listOf()
}
fun findEligibleForInternalName(internalName: SkyblockId): List<Reforge> {
return byInternalName[internalName] ?: listOf()
}
//TODO: return byVanillla
override fun reload(repo: NEURepository) {
val basicReforges =
repo.file("constants/reforges.json")
?.kJson(serializer<Map<String, Reforge>>())
?.values ?: emptyList()
val advancedReforges =
repo.file("constants/reforgestones.json")
?.kJson(serializer<Map<String, Reforge>>())
?.values ?: emptyList()
val allReforges = (basicReforges + advancedReforges)
modifierLut = allReforges.associateBy { it.reforgeId }
byReforgeStone = allReforges.filter { it.reforgeStone != null }
.associateBy { it.reforgeStone!! }
val byType = mutableMapOf<ItemType, MutableList<Reforge>>()
val byVanilla = mutableMapOf<RegistryKey<Item>, MutableList<Reforge>>()
val byInternalName = mutableMapOf<SkyblockId, MutableList<Reforge>>()
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<ItemType> {
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 <T> NEURepoFile.kJson(serializer: KSerializer<T>): 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)
}
}
}

View File

@@ -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<ItemType?, List<NEUItem>> = mapOf()
override fun reload(repository: NEURepository) {
byItemType = repository.items.items.values.groupBy { ItemType.fromEscapeCodeLore(it.lore.lastOrNull() ?: "") }
}
}

View File

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

View File

@@ -9,18 +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.repo.ItemCache.withFallback
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.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(
@@ -29,9 +42,9 @@ data class SBItemStack constructor(
private var stackSize: Int,
private var petData: PetData?,
val extraLore: List<Text> = emptyList(),
// TODO: grab this star data from nbt if possible
val stars: Int = 0,
val fallback: ItemStack? = null,
val reforge: ReforgeId? = null,
) {
fun getStackSize() = stackSize
@@ -68,7 +81,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()
)
}
@@ -83,6 +98,153 @@ data class SBItemStack constructor(
fun passthrough(itemStack: ItemStack): SBItemStack {
return SBItemStack(SkyblockId.NULL, null, itemStack.count, null, fallback = itemStack)
}
fun appendEnhancedStats(
itemStack: ItemStack,
reforgeStats: Map<String, Double>,
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),
"Ability Damage" to StatFormatting("%", Formatting.RED),
"Trophy Fish Chance" to StatFormatting("%", Formatting.GREEN),
"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
)
private val statLabelRegex = "(?<statName>.*): ".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<Text> = 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)
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("${abbreviate(abbreviateTo)}: ").grey())
.append(value ?: formatValue())
.also { rest.forEach(it::append) }
}
fun statIdToName(statId: String): String {
val segments = statId.split("_")
return segments.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(
@@ -133,6 +295,25 @@ data class SBItemStack constructor(
}
private fun appendReforgeInfo(
itemStack: ItemStack,
) {
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
val itemType: ItemType? get() = ItemType.fromItemStack(asImmutableItemStack())
val rarity: Rarity? get() = Rarity.fromItem(asImmutableItemStack())
private var itemStack_: ItemStack? = null
private val itemStack: ItemStack
@@ -147,6 +328,7 @@ data class SBItemStack constructor(
return@run neuItem.asItemStack(idHint = skyblockId, replacementData)
.withFallback(fallback)
.copyWithCount(stackSize)
.also { appendReforgeInfo(it) }
.also { it.appendLore(extraLore) }
.also { enhanceStatsByStars(it, stars) }
}