aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2025-11-18 22:17:39 +0100
committerLinnea Gräf <nea@nea.moe>2025-11-18 22:17:39 +0100
commit5d9b084be9af0f97adad002f3389add22ebf07e5 (patch)
treeaa79511785386e4af6672429142bd3b38c94edd9
parent54206c51148255320ad8bc8d03c122d24b98b459 (diff)
downloadFirmament-5d9b084be9af0f97adad002f3389add22ebf07e5.tar.gz
Firmament-5d9b084be9af0f97adad002f3389add22ebf07e5.tar.bz2
Firmament-5d9b084be9af0f97adad002f3389add22ebf07e5.zip
feat: add generic reforge renderer
-rw-r--r--src/main/kotlin/gui/entity/EntityRenderer.kt3
-rw-r--r--src/main/kotlin/repo/recipes/GenericRecipeRenderer.kt2
-rw-r--r--src/main/kotlin/repo/recipes/RecipeLayouter.kt33
-rw-r--r--src/main/kotlin/repo/recipes/SBReforgeRecipeRenderer.kt167
4 files changed, 200 insertions, 5 deletions
diff --git a/src/main/kotlin/gui/entity/EntityRenderer.kt b/src/main/kotlin/gui/entity/EntityRenderer.kt
index 2381b69..4972709 100644
--- a/src/main/kotlin/gui/entity/EntityRenderer.kt
+++ b/src/main/kotlin/gui/entity/EntityRenderer.kt
@@ -3,6 +3,7 @@ package moe.nea.firmament.gui.entity
import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonObject
+import me.shedaniel.math.Dimension
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.atan
@@ -236,5 +237,5 @@ object EntityRenderer {
context.disableScissor()
}
-
+ val defaultSize = Dimension(50, 80)
}
diff --git a/src/main/kotlin/repo/recipes/GenericRecipeRenderer.kt b/src/main/kotlin/repo/recipes/GenericRecipeRenderer.kt
index 6bc79d5..c029494 100644
--- a/src/main/kotlin/repo/recipes/GenericRecipeRenderer.kt
+++ b/src/main/kotlin/repo/recipes/GenericRecipeRenderer.kt
@@ -8,7 +8,7 @@ import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import moe.nea.firmament.repo.SBItemStack
-interface GenericRecipeRenderer<T : NEURecipe> {
+interface GenericRecipeRenderer<T : Any> {
fun render(recipe: T, bounds: Rectangle, layouter: RecipeLayouter, mainItem: SBItemStack?)
fun getInputs(recipe: T): Collection<SBItemStack>
fun getOutputs(recipe: T): Collection<SBItemStack>
diff --git a/src/main/kotlin/repo/recipes/RecipeLayouter.kt b/src/main/kotlin/repo/recipes/RecipeLayouter.kt
index 2c013b7..b211d9c 100644
--- a/src/main/kotlin/repo/recipes/RecipeLayouter.kt
+++ b/src/main/kotlin/repo/recipes/RecipeLayouter.kt
@@ -4,6 +4,9 @@ import io.github.notenoughupdates.moulconfig.gui.GuiComponent
import me.shedaniel.math.Point
import me.shedaniel.math.Rectangle
import net.minecraft.network.chat.Component
+import net.minecraft.world.entity.Entity
+import net.minecraft.world.entity.LivingEntity
+import net.minecraft.world.entity.npc.Villager
import moe.nea.firmament.repo.SBItemStack
interface RecipeLayouter {
@@ -15,24 +18,48 @@ interface RecipeLayouter {
* Create a bigger background and mark the slot as output. The coordinates should still refer the upper left corner of the item stack, not of the bigger background.
*/
BIG_OUTPUT,
+ DISPLAY,
}
+
+ fun createCyclingItemSlot(
+ x: Int, y: Int,
+ content: List<SBItemStack>,
+ slotKind: SlotKind
+ ): CyclingItemSlot
+
fun createItemSlot(
x: Int, y: Int,
content: SBItemStack?,
slotKind: SlotKind,
- )
+ ): ItemSlot
+
+ interface CyclingItemSlot : ItemSlot {
+ fun onUpdate(action: () -> Unit)
+ }
+
+ interface ItemSlot : Updater<SBItemStack> {
+ fun current(): SBItemStack
+ }
+
+ interface Updater<T> {
+ fun update(newValue: T)
+ }
+
+ fun createTooltip(rectangle: Rectangle, label: List<Component>)
+ fun createTooltip(rectangle: Rectangle, vararg label: Component) =
+ createTooltip(rectangle, label.toList())
- fun createTooltip(rectangle: Rectangle, label: Component)
fun createLabel(
x: Int, y: Int,
text: Component
- )
+ ): Updater<Component>
fun createArrow(x: Int, y: Int): Rectangle
fun createMoulConfig(x: Int, y: Int, w: Int, h: Int, component: GuiComponent)
fun createFire(ingredientsCenter: Point, animationTicks: Int)
+ fun createEntity(rectangle: Rectangle, entity: LivingEntity)
}
diff --git a/src/main/kotlin/repo/recipes/SBReforgeRecipeRenderer.kt b/src/main/kotlin/repo/recipes/SBReforgeRecipeRenderer.kt
new file mode 100644
index 0000000..c841dd9
--- /dev/null
+++ b/src/main/kotlin/repo/recipes/SBReforgeRecipeRenderer.kt
@@ -0,0 +1,167 @@
+package moe.nea.firmament.repo.recipes
+
+import io.github.moulberry.repo.NEURepository
+import me.shedaniel.math.Point
+import me.shedaniel.math.Rectangle
+import net.minecraft.network.chat.Component
+import net.minecraft.resources.ResourceLocation
+import net.minecraft.world.entity.EntitySpawnReason
+import net.minecraft.world.entity.EntityType
+import net.minecraft.world.entity.npc.VillagerProfession
+import net.minecraft.world.item.ItemStack
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.gui.entity.EntityRenderer
+import moe.nea.firmament.repo.ExpensiveItemCacheApi
+import moe.nea.firmament.repo.Reforge
+import moe.nea.firmament.repo.ReforgeStore
+import moe.nea.firmament.repo.RepoItemTypeCache
+import moe.nea.firmament.repo.SBItemStack
+import moe.nea.firmament.util.FirmFormatters.formatCommas
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.gold
+import moe.nea.firmament.util.grey
+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
+
+object SBReforgeRecipeRenderer : GenericRecipeRenderer<Reforge> {
+ @OptIn(ExpensiveItemCacheApi::class)
+ override fun render(
+ recipe: Reforge,
+ bounds: Rectangle,
+ layouter: RecipeLayouter,
+ mainItem: SBItemStack?
+ ) {
+ val inputSlot = layouter.createCyclingItemSlot(
+ bounds.minX + 10, bounds.centerY - 9,
+ if (mainItem != null) listOf(mainItem)
+ else generateAllItems(recipe),
+ RecipeLayouter.SlotKind.SMALL_INPUT
+ )
+ val outputSlut = layouter.createItemSlot(
+ bounds.minX + 10 + 24 + 24, bounds.centerY - 9,
+ null,
+ RecipeLayouter.SlotKind.SMALL_OUTPUT
+ )
+ val statLines = mutableListOf<Pair<String, RecipeLayouter.Updater<Component>>>()
+ for ((i, statId) in recipe.statUniverse.withIndex()) {
+ val label = layouter.createLabel(
+ bounds.minX + 10 + 24 + 24 + 20, bounds.minY + 8 + i * 11,
+ Component.empty()
+ )
+ statLines.add(statId to label)
+ }
+
+ fun updateOutput() {
+ val currentBaseItem = inputSlot.current()
+ outputSlut.update(currentBaseItem.copy(reforge = recipe.reforgeId))
+ val stats = recipe.reforgeStats?.get(currentBaseItem.rarity) ?: mapOf()
+ for ((stat, label) in statLines) {
+ label.update(
+ SBItemStack.Companion.StatLine(
+ SBItemStack.statIdToName(stat), null,
+ valueNum = stats[stat]
+ ).reconstitute(7)
+ )
+ }
+ }
+
+ if (recipe.reforgeStone != null) {
+ layouter.createItemSlot(
+ bounds.minX + 10 + 24, bounds.centerY - 9 - 10,
+ SBItemStack(recipe.reforgeStone),
+ RecipeLayouter.SlotKind.SMALL_INPUT
+ )
+ val d = Rectangle(
+ bounds.minX + 10 + 24, bounds.centerY - 9 + 10,
+ 16, 16
+ )
+ layouter.createItemSlot(
+ d.x, d.y,
+ SBItemStack(SkyBlockItems.REFORGE_ANVIL),
+ RecipeLayouter.SlotKind.DISPLAY
+ )
+ layouter.createTooltip(
+ d,
+ Rarity.entries.mapNotNull { rarity ->
+ recipe.reforgeCosts?.get(rarity)?.let { rarity to it }
+ }.map { (rarity, cost) ->
+ Component.literal("")
+ .append(rarity.text)
+ .append(": ")
+ .append(Component.literal("${formatCommas(cost, 0)} Coins").gold())
+ }
+ )
+ } else {
+ val entity = EntityType.VILLAGER.create(EntityRenderer.fakeWorld, EntitySpawnReason.COMMAND)
+ ?.also {
+ it.villagerData =
+ it.villagerData.withProfession(
+ MC.currentOrDefaultRegistries,
+ VillagerProfession.WEAPONSMITH
+ )
+ }
+ val dim = EntityRenderer.defaultSize
+ val d = Rectangle(
+ Point(bounds.minX + 10 + 24 + 8 - dim.width / 2, bounds.centerY - dim.height / 2),
+ dim
+ )
+ if (entity != null)
+ layouter.createEntity(
+ d,
+ entity
+ )
+ layouter.createTooltip(
+ d,
+ tr(
+ "firmament.recipecategory.reforge.basic",
+ "This is a basic reforge, available at the Blacksmith."
+ ).grey()
+ )
+ }
+ }
+
+ private fun generateAllItems(recipe: Reforge): List<SBItemStack> {
+ return recipe.eligibleItems.flatMap {
+ when (it) {
+ is Reforge.ReforgeEligibilityFilter.AllowsInternalName -> listOf(SBItemStack(it.internalName))
+ is Reforge.ReforgeEligibilityFilter.AllowsItemType ->
+ ReforgeStore.resolveItemType(it.itemType)
+ .flatMapTo(mutableSetOf()) { itemType ->
+ listOf(itemType, itemType.dungeonVariant)
+ }
+ .flatMapTo(mutableSetOf()) { itemType ->
+ RepoItemTypeCache.byItemType[itemType] ?: listOf()
+ }
+ .map { SBItemStack(it.skyblockId) }
+
+ is Reforge.ReforgeEligibilityFilter.AllowsVanillaItemType -> listOf()
+ }
+ }
+ }
+
+ override fun getInputs(recipe: Reforge): Collection<SBItemStack> {
+ val reforgeStone = recipe.reforgeStone ?: return emptyList()
+ return listOf(SBItemStack(reforgeStone))
+ }
+
+ override fun getOutputs(recipe: Reforge): Collection<SBItemStack> {
+ return listOf()
+ }
+
+ @OptIn(ExpensiveItemCacheApi::class)
+ override val icon: ItemStack
+ get() = SBItemStack(SkyBlockItems.REFORGE_ANVIL).asImmutableItemStack()
+ override val title: Component
+ get() = tr("firmament.recipecategory.reforge", "Reforge")
+ override val identifier: ResourceLocation
+ get() = Firmament.identifier("reforge_recipe")
+
+ override fun findAllRecipes(neuRepository: NEURepository): Iterable<Reforge> {
+ return ReforgeStore.allReforges
+ }
+
+ override val typ: Class<Reforge>
+ get() = Reforge::class.java
+}