WIP: Reforge Recipes

This commit is contained in:
Linnea Gräf
2024-12-25 16:15:20 +01:00
parent e16c60169b
commit ddebaf4790
14 changed files with 273 additions and 48 deletions

View File

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

View File

@@ -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<Widget> {
val list = mutableListOf<Widget>()
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 {

View File

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

View File

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

View File

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

View File

@@ -108,6 +108,8 @@ data class Reforge(
@Serializable(with = RarityMapped.Serializer::class)
sealed interface RarityMapped<T> {
fun get(rarity: Rarity): T?
class Serializer<T>(
val values: KSerializer<T>
) : KSerializer<RarityMapped<T>> {
@@ -137,10 +139,18 @@ data class Reforge(
}
@Serializable
data class Direct<T>(val value: T) : RarityMapped<T>
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>
data class PerRarity<T>(val values: Map<Rarity, T>) : RarityMapped<T> {
override fun get(rarity: Rarity): T? {
return values[rarity]
}
}
}
}

View File

@@ -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<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),
"Trophy Fish Chance" to StatFormatting("%", Formatting.GREEN),
// TODO: add other types and 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)
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)

View File

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

View File

@@ -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 {

View File

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

View File

@@ -5,7 +5,7 @@ import net.minecraft.component.type.LoreComponent
import net.minecraft.item.ItemStack
import net.minecraft.text.Text
var ItemStack.loreAccordingToNbt
var ItemStack.loreAccordingToNbt: List<Text>
get() = get(DataComponentTypes.LORE)?.lines ?: listOf()
set(value) {
set(DataComponentTypes.LORE, LoreComponent(value))

View File

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

View File

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

View File

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