aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2025-03-17 22:49:49 +0100
committerLinnea Gräf <nea@nea.moe>2025-03-17 22:49:49 +0100
commitaacd527cb8e226b92e5ad28d8d075da06fd79ec4 (patch)
tree2a2745cd528e0302aa9ebe46d6a46a04c80507c4 /src
parent207161dc5e2ae93648b563bae7b7427ec2524d64 (diff)
downloadFirmament-aacd527cb8e226b92e5ad28d8d075da06fd79ec4.tar.gz
Firmament-aacd527cb8e226b92e5ad28d8d075da06fd79ec4.tar.bz2
Firmament-aacd527cb8e226b92e5ad28d8d075da06fd79ec4.zip
refactor: Refactor stat blocks
Diffstat (limited to 'src')
-rw-r--r--src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBReforgeRecipe.kt12
-rw-r--r--src/main/kotlin/repo/SBItemStack.kt140
-rw-r--r--src/main/kotlin/repo/item/StatBlock.kt163
3 files changed, 182 insertions, 133 deletions
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 b8313a6..8e86967 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
@@ -31,6 +31,7 @@ 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.repo.item.StatBlock
import moe.nea.firmament.util.AprilFoolsUtil
import moe.nea.firmament.util.FirmFormatters
import moe.nea.firmament.util.SkyblockId
@@ -106,7 +107,10 @@ class SBReforgeRecipe(
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))
+ StatBlock.StatLine( // TODO: add helper methods for constructing stat lines
+ StatBlock.findStatFormatting(SBItemStack.statIdToName(statId)),
+ 0.0
+ ).reconstitute(7))
.horizontalAlignment(Label.LEFT_ALIGNED)
statToLineMappings.add(statId to label)
list.add(label)
@@ -116,9 +120,9 @@ class SBReforgeRecipe(
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]
+ StatBlock.StatLine(
+ StatBlock.findStatFormatting(SBItemStack.statIdToName(stat)),
+ stats[stat] ?: 0.0
).reconstitute(7)
}
}
diff --git a/src/main/kotlin/repo/SBItemStack.kt b/src/main/kotlin/repo/SBItemStack.kt
index 3690866..93cf52c 100644
--- a/src/main/kotlin/repo/SBItemStack.kt
+++ b/src/main/kotlin/repo/SBItemStack.kt
@@ -11,17 +11,16 @@ 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.repo.item.StatBlock
import moe.nea.firmament.util.FirmFormatters
import moe.nea.firmament.util.LegacyFormattingCode
import moe.nea.firmament.util.MC
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
@@ -107,13 +106,6 @@ data class SBItemStack constructor(
return SBItemStack(SkyblockId.NULL, null, itemStack.count, null, fallback = itemStack)
}
- fun parseStatBlock(itemStack: ItemStack): List<StatLine> {
- return itemStack.loreAccordingToNbt
- .map { parseStatLine(it) }
- .takeWhile { it != null }
- .filterNotNull()
- }
-
fun appendEnhancedStats(
itemStack: ItemStack,
reforgeStats: Map<String, Double>,
@@ -121,81 +113,23 @@ data class SBItemStack constructor(
) {
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.
+ val statBlock = StatBlock.fromLore(loreMut)
for ((statName, statBuff) in namedReforgeStats) {
- val statLine = StatLine(statName, null).addStat(statBuff, buffKind)
- loreMut.add(statBlockLastIndex + 1, statLine.reconstitute())
+ statBlock.modify(statName) { it.addStat(statBuff, buffKind) }
}
+ statBlock.applyModifications(loreMut)
itemStack.loreAccordingToNbt = loreMut
}
data class StatFormatting(
+ val name: String,
val postFix: String,
val color: Formatting,
val isStarAffected: Boolean = true,
)
- 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.GREEN, false),
- "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,
@@ -208,62 +142,10 @@ data class SBItemStack constructor(
;
}
- data class StatLine(
- val statName: String,
- val value: Text?,
- val rest: List<Text> = listOf(),
- val valueNum: Double? = value?.directLiteralStringContent?.trim(' ', 's', '%', '+')?.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 +
- if (buffKind.isHidden) emptyList()
- else 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(
@@ -369,7 +251,7 @@ data class SBItemStack constructor(
val baseItem = neuItem.asItemStack(idHint = skyblockId, replacementData)
.withFallback(fallback)
.copyWithCount(stackSize)
- val baseStats = parseStatBlock(baseItem)
+ val baseStats = StatBlock.fromLore(baseItem.loreAccordingToNbt)
appendReforgeInfo(baseItem)
baseItem.appendLore(extraLore)
enhanceStatsByStars(baseItem, stars, baseStats)
@@ -403,9 +285,8 @@ data class SBItemStack constructor(
return starString
}
- private fun enhanceStatsByStars(itemStack: ItemStack, stars: Int, baseStats: List<StatLine>) {
+ private fun enhanceStatsByStars(itemStack: ItemStack, stars: Int, baseStats: StatBlock) {
if (stars == 0) return
- // TODO: increase stats and add the star level into the nbt data so star displays work
itemStack.modifyExtraAttributes {
it.putInt("upgrade_level", stars)
}
@@ -415,9 +296,10 @@ data class SBItemStack constructor(
val truncatedStarCount = if (isDungeon) minOf(5, stars) else stars
appendEnhancedStats(itemStack,
baseStats
- .filter { it.statFormatting.isStarAffected }
+ .indexedByName.values
+ .filter { it.stat.isStarAffected }
.associate {
- it.statName to ((it.valueNum ?: 0.0) * (truncatedStarCount * 0.02))
+ it.statName to (it.value * (truncatedStarCount * 0.02))
},
BuffKind.STAR_BUFF)
}
diff --git a/src/main/kotlin/repo/item/StatBlock.kt b/src/main/kotlin/repo/item/StatBlock.kt
new file mode 100644
index 0000000..94643a2
--- /dev/null
+++ b/src/main/kotlin/repo/item/StatBlock.kt
@@ -0,0 +1,163 @@
+package moe.nea.firmament.repo.item
+
+import kotlin.collections.forEach
+import kotlin.collections.plus
+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.SBItemStack.Companion.BuffKind
+import moe.nea.firmament.repo.SBItemStack.Companion.StatFormatting
+import moe.nea.firmament.util.FirmFormatters
+import moe.nea.firmament.util.directLiteralStringContent
+import moe.nea.firmament.util.grey
+import moe.nea.firmament.util.useMatch
+import moe.nea.firmament.util.withColor
+
+data class StatBlock(
+ val indexedByName: Map<String, StatLine>,
+ val startIndex: Int,
+ val endIndex: Int,
+ private val modifiedLines: MutableMap<String, StatLine> = mutableMapOf()
+) {
+
+ /**
+ * Note that the returned stat line must be created by copying from the original stat line (to keep indexes in sync).
+ */
+ fun modify(statName: String, mod: (StatLine) -> StatLine) {
+ val existing = modifiedLines[statName] ?: indexedByName[statName] ?: StatLine(findStatFormatting(statName), 0.0)
+ modifiedLines[statName] = mod(existing)
+ }
+
+ fun applyModifications(lore: MutableList<Text>) {
+ if (modifiedLines.isEmpty()) return
+ var nextAppendIndex = endIndex
+ if (startIndex < 0) // No existing stat block, insert the after space.
+ lore.add(0, Text.literal(""))
+ modifiedLines.values.forEach { line ->
+ val loreLine = if (line.loreIndex < 0) {
+ lore.add(nextAppendIndex, Text.literal(""))
+ nextAppendIndex++
+ } else line.loreIndex
+ lore[loreLine] = line.reconstitute()
+ }
+ }
+
+ companion object {
+ fun fromLore(lore: List<Text>): StatBlock {
+ val map = mutableMapOf<String, StatLine>()
+ var start = -1
+ var end = 0
+ for ((index, text) in lore.withIndex()) {
+ val statLine = parseStatLine(text)
+ ?.copy(loreIndex = index)
+ if (statLine == null) {
+ if (start < 0) continue
+ else break
+ }
+ map[statLine.statName] = statLine
+ if (start < 0)
+ start = index
+ end = index + 1
+ }
+ return StatBlock(map, start, end)
+ }
+
+ val allFormattingOverrides = listOf(
+ StatFormatting("Sea Creature Chance", "%", Formatting.RED),
+ StatFormatting("Strength", "", Formatting.RED),
+ StatFormatting("Damage", "", Formatting.RED),
+ StatFormatting("Bonus Attack Speed", "%", Formatting.RED),
+ StatFormatting("Shot Cooldown", "s", Formatting.GREEN, false),
+ StatFormatting("Ability Damage", "%", Formatting.RED),
+ StatFormatting("Crit Damage", "%", Formatting.RED),
+ StatFormatting("Crit Chance", "%", Formatting.RED),
+ StatFormatting("Ability Damage", "%", Formatting.RED),
+ StatFormatting("Trophy Fish Chance", "%", Formatting.GREEN),
+ StatFormatting("Health", "", Formatting.GREEN),
+ StatFormatting("Defense", "", Formatting.GREEN),
+ StatFormatting("Fishing Speed", "", Formatting.GREEN),
+ StatFormatting("Double Hook Chance", "%", Formatting.GREEN),
+ StatFormatting("Mining Speed", "", Formatting.GREEN),
+ StatFormatting("Mining Fortune", "", Formatting.GREEN),
+ StatFormatting("Heat Resistance", "", Formatting.GREEN),
+ StatFormatting("Swing Range", "", Formatting.GREEN),
+ StatFormatting("Rift Time", "", Formatting.GREEN),
+ StatFormatting("Speed", "", Formatting.GREEN),
+ StatFormatting("Farming Fortune", "", Formatting.GREEN),
+ StatFormatting("True Defense", "", Formatting.GREEN),
+ StatFormatting("Mending", "", Formatting.GREEN),
+ StatFormatting("Foraging Wisdom", "", Formatting.GREEN),
+ StatFormatting("Farming Wisdom", "", Formatting.GREEN),
+ StatFormatting("Foraging Fortune", "", Formatting.GREEN),
+ StatFormatting("Magic Find", "", Formatting.GREEN),
+ StatFormatting("Ferocity", "", Formatting.GREEN),
+ StatFormatting("Bonus Pest Chance", "%", Formatting.GREEN),
+ StatFormatting("Cold Resistance", "", Formatting.GREEN),
+ StatFormatting("Pet Luck", "", Formatting.GREEN),
+ StatFormatting("Fear", "", Formatting.GREEN),
+ StatFormatting("Mana Regen", "%", Formatting.GREEN),
+ StatFormatting("Rift Damage", "", Formatting.GREEN),
+ StatFormatting("Hearts", "", Formatting.GREEN),
+ StatFormatting("Vitality", "", Formatting.GREEN),
+ // TODO: make this a repo json
+ )
+ val formattingOverrides = allFormattingOverrides.associateBy { it.name }
+ fun findStatFormatting(name: String) =
+ formattingOverrides[name] ?: StatFormatting(name, "", Formatting.GREEN)
+
+ private val statLabelRegex = "(?<statName>.*): ".toPattern()
+ 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(findStatFormatting(statName),
+ sibs[1]?.directLiteralStringContent?.trim(' ', 's', '%', '+')?.toDoubleOrNull() ?: 0.0,
+ sibs.subList(2, sibs.size))
+ }
+
+ }
+
+ data class StatLine(
+ val stat: StatFormatting,
+ val value: Double,
+ val modifiers: List<Text> = listOf(),
+ val loreIndex: Int = -1,
+ ) {
+ fun formatValue() =
+ Text.literal(FirmFormatters.formatCommas(
+ value, 1, includeSign = true) + stat.postFix + " ")
+ .setStyle(Style.EMPTY.withColor(stat.color))
+
+ val statName get() = stat.name
+ fun reconstitute(abbreviateTo: Int = Int.MAX_VALUE): Text =
+ Text.literal("").setStyle(Style.EMPTY.withItalic(false))
+ .append(Text.literal("${abbreviate(abbreviateTo)}: ").grey())
+ .append(formatValue())
+ .also { modifiers.forEach(it::append) }
+
+ fun addStat(amount: Double, buffKind: BuffKind): StatLine {
+ val formattedAmount = FirmFormatters.formatCommas(amount, 1, includeSign = true)
+ return copy(
+ value = value + amount,
+ modifiers = modifiers +
+ if (buffKind.isHidden) emptyList()
+ else listOf(
+ Text.literal(
+ buffKind.prefix + formattedAmount +
+ stat.postFix +
+ buffKind.postFix + " ")
+ .withColor(buffKind.color)))
+ }
+
+ 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))
+ }
+ }
+ }
+}